回收者视图变得太复杂了



>上下文

所以,我不知道你们中是否有人经历过同样的情况,但我最近接管了一个应用程序,我们在主屏幕上有这个 RecyclerView - 因为 NDA,我会改变一些东西 - 显示您可以租用的公寓列表 - 想象一下 AirBnB 应用程序 - 如果你点击这些公寓项目之一,你会转到公寓详细信息, 您拥有更多功能和特性的地方。

问题是我们在公寓清单上有太多的活动部件。例如,在每个公寓的ViewHolder上,您可以:

  • 使用复选标记指定是否要携带任何宠物。
  • 一些 UI 项,用于指定要停留多长时间。
  • 一个编辑文本,用于设置人们将如何来。
  • 一个 Rent 按钮,可将自身变成微调器并发送 API 调用。
  • "更多选项"按钮,用于展开 ViewHolder,显示具有更多 UI 的线性布局。

想象一下这样的东西

这实际上是一个更简单的例子,说明我真正拥有的东西。让我告诉你,看起来每个 ViewHolder 都可以是一个片段,因为我们在每个 ViewHolder 上都有所有功能。

现在这里有什么问题?

回收问题。如果您向下滚动,然后滚动回相同的位置,您应该保持在该 ViewHolder 上相同的状态,对吗?如果您选中了应该检查的复选框。如果你在EditText上写了一些东西,那应该在那里。如果您已展开"更多选项"部分,则应将其展开。你看我要去哪里?

我在这里问什么?

好吧,关于可能的解决方案或改进的反馈。我知道你们大多数人会在这里告诉我什么 - 因为这和我最初的想法是一样的 - 只需将所有功能移动到公寓细节中,保持该列表尽可能简单。但这并不那么简单,我们拥有庞大的用户群,他们已经习惯了这个 UI。如此突然地改变事情不是一种选择。

我现在有什么?

在我的RecyclerView适配器中,我保留了一组"状态"对象,用于保存/恢复ViewHolder状态,但它变得太大,太复杂了。这听起来可能很疯狂,但它有没有像回收器碎片列表这样的事情?我只是不想再担心/打扰保留这些 ViewHolder 的状态。

笔记

抱歉,我没有提供任何代码,但实际上并没有太多要显示的内容,因为您可能会想象onBindViewHolder只是一段巨大的代码,它使用我从 API 获取的数据以及我存储在这些"State"对象中的数据来设置视图。我通过适配器类中的onViewDetachedFromWindows()钩保存这些"State"对象,当 ViewHolder 从屏幕上滚动时触发该适配器类。当我获取新的 API 响应时,我会清除这些"状态"对象。

任何反馈不胜感激, 谢谢!

您的帖子在高级描述中含糊不清,但我会尝试以类似的方式发表评论,这可能会指导您找到解决方案。

首先,正如已经提到的,环氧树脂是一个东西。适配器委托也是如此。您可能会发现这些有用。但是,您不需要库来解决您的问题 - 您需要分离关注点架构


问题是我们在公寓清单上有太多的活动部件。

好的,所以第一个建议是停止在列表中有太多的活动部件。您列出的每件事都可以/应该是它自己的(自定义)视图,由它自己的视图模型驱动。回收器视图/视图持有人/适配器应尽可能愚蠢。所有这些事情应该做的是填写Android所需的样板。实际逻辑应该存在于其他地方。

如果您向下滚动,然后滚动回相同的位置,您应该保持在该 ViewHolder 上相同的状态,对吗?

不。您的ViewHolder不应保持状态。ViewHolder保存视图,因此Android不必一遍又一遍地重新膨胀内容。它不应该跟踪它的状态 - 它应该被告知它的当前状态是什么。

您应该有一个数据对象(视图模型)列表,这些对象表示列表中每个项的当前状态。当您向下滚动并滚回同一位置时,您应该将应该位于该位置的项重新绑定到表示它的视图。保存和清除"状态"对象不是必需的 - 您应该始终拥有当前状态,因为它是驱动整个 UI 的基础数据模型。

在我的RecyclerView适配器中,我保留了一组"状态"对象,用于保存/恢复ViewHolder状态,但它变得太大,太复杂

如果某件事太大太复杂,请将其分解。与其为每个项目设置一个巨型状态对象,不如使用组合。使此项状态具有表示 UI 各个部分的属性 - PetModel、DateRangeModel 等。

这听起来可能很疯狂,但它有没有像回收器碎片列表这样的事情?我只是不想再担心/打扰保留这些 ViewHolder 的状态。

这听起来确实很疯狂,因为这不仅不能解决你的问题,而且实际上可能会让它变得更糟。您不想管理一堆视图持有者的状态,但您想管理一堆片段的状态!?布鲁。

正如您可能想象的那样,onBindViewHolder 只是一段巨大的代码,它使用我从 API 获取的数据以及我存储在这些"State"对象中的数据来设置视图。

再次,打破它。您不应该将"我从 API 获取的数据"直接拍打到视图上。在显示原始数据之前,您总是需要从 API 中调整和转换原始数据。这应该由专用对象(同样,ViewModel 或其他结构)处理。同样,观点应该是愚蠢的。告诉他们他们的状态,仅此而已 - 不要在这个级别上做逻辑。

请阅读安卓架构指南。 还有谷歌搜索"干净的架构" - 这似乎是现在Android的所有范围。

最后 - 这里有一些非常粗略的伪代码,说明如何构建它以使其更易于测试和维护。

自下而上:

  • ApiClient- 负责仅从 API 获取原始数据 终结点或报告错误。
  • ApiResponseModel- 特定于语言的对象表示形式 将从 API 获取的数据。有关于宠物的信息,日期, 客人数量等。可能包含子模型。
  • ItemDomainModel- 转换从 API 获取的数据后数据的客户端表示形式。
  • Repository- 使用ApiClient提取数据作为ApiResponseModel,并将其转换为对应用更有意义的ItemDomainModel对象。
  • ItemViewModel- 表示回收程序视图中单个项的 UI 状态。获取ItemDomainModel实例,并根据该模型的状态公开 UI 的状态。如果它太复杂(PetStateViewModelDateRangeViewModelGuestCountViewModel等),可以分解。
  • ListViewModel- 表示屏幕状态的顶级 Android 视图模型。使用Repository获取数据,然后构造一个ItemViewModel列表以馈送到RecyclerViewAdapter中。

如果将这些部分就位,则适配器中的视图绑定应该是愚蠢的愚蠢的:

override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// The adapter list should be a list of view models populated by the
// fragment after the ListViewModel returns a list of them from the fetch
val itemViewModel = itemViewModels[position]

// Populating this item view should just be a one-to-one mapping of the view model
// state - NO LOGIC. Dumb. Stupid. Tonto.
viewHolder.bringingPets.isChecked = itemViewModel.isBringingPets
viewHolder.guestCount.text = itemViewModel.guestCount
// ... etc, etc (if you use databinding this is a one-liner and even stupider)
// Set up your event listeners so interacting with this specific item in the list
// updates the state of the underlying data model
viewHolder.bringingPets.setOnCheckChanged { itemViewModel.isBringingPets = it.isChecked }
viewHolder.rentButton.onClickListener { itemViewModel.rentThis() }
// ... etc, etc
}

目标是在这里尽可能地做。只需更新状态并连接仅委派回 ViewModel 的回调。然后,这些 UI 状态由视图模型中的逻辑驱动。这是您执行业务逻辑的位置,该逻辑确定 UI 的外观。

class ItemViewModel(private val dataModel: ItemDomainModel) {
var isBringingPets: Boolean
get() = /* some business logic that determines if the checkbox is checked */
set(value) /* update underlying state and notify of changes */
// ... etc, etc, for guest count and other properties
fun rentThis() {
// Fire an event or update live data or invoke a callback that
// the fragment can use to respond
}
// ... etc, etc, for other functions that respond to UI events
}

总结

重构代码,将庞大而复杂的逻辑分解为专用组件,每个组件都有更简单、更具体的焦点,然后将它们组合在一起以获得所需的行为。祝你好运。

最新更新