【问题标题】:Best practice using RecyclerView, ListAdapter and ViewModel for saving checked status使用 RecyclerView、ListAdapter 和 ViewModel 保存检查状态的最佳实践
【发布时间】:2020-02-19 07:43:25
【问题描述】:

我正在使用带有 ListAdapter 的 RecyclerView,它在内部使用 DiffUtil 来显示数据。当然,所有数据都是 ViewModel 通过LiveData提供的。

现在每个项目视图都有一个CheckBox,同时我通过添加RecyclerView.SimpleOnItemTouchListener实现了拖动选择功能。选择状态现在由适配器维护,这不是一个好主意,因为屏幕旋转后状态会丢失。

我想让 ViewModel 保存选择状态,这是我的想法:

  1. Fragment 在收到相关回调时会调用 ViewModel 改变状态。
  2. ViewModel 将更改数据列表(如果它不可变,则创建一个新列表)并发布到 LiveData。
  3. Fragment 接收到数据变化并提交给ListAdapter

问题是如果用户激活拖动选择,选择状态可能会非常频繁地变化。 DifferUtil如果有很多数据就得花很长时间比较。与在适配器中保存状态不同,现在我无法准确通知项目更改,因为我不知道更改了哪个位置。

MVVM 的核心思想是数据驱动视图,但在这种情况下,我们丢失了额外的信息(例如位置),导致额外的计算。那么最佳做法是什么?

部分适配器代码(基于扩展ListAdapterGroupListAdapter):

    private val selectedGroups = mutableSetOf<Int>()
    private val selectedImages = mutableSetOf<Image>()

    override fun onBindGroupViewHolder(holder: StubViewHolder, groupIndex: Int, flattedPosition: Int) {
        holder.setText(R.id.itemImageTitleTextView, getGroupItem(groupIndex).title)
    }

    override fun onBindChildViewHolder(holder: StubViewHolder, groupIndex: Int, childIndex: Int, flattedPosition: Int) {
        val data = getChildItem(groupIndex, childIndex)
        GlideApp.with(fragment).load(data.uri).into(holder.getView(R.id.itemImageView))
    }

    override fun onBindGroupViewHolder(holder: StubViewHolder, groupIndex: Int, flattedPosition: Int,
                                       payloads: MutableList<Any>) {
        holder.setGroupSelectionMode(isInSelectMode())
        holder.setGroupChecked(selectedGroups.contains(flattedPosition))
    }

    override fun onBindChildViewHolder(holder: StubViewHolder, groupIndex: Int, childIndex: Int, flattedPosition: Int,
                                       payloads: MutableList<Any>) {
        if (isInSelectMode()) {
            holder.setImageSelectionMode(true)
            if (selectedImages.contains(getItem(flattedPosition))) {
                holder.getView<MaskImageView>(R.id.itemImageView).isChecked = true
                holder.setImageChecked(true)
            } else {
                holder.getView<MaskImageView>(R.id.itemImageView).isChecked = false
                holder.setImageChecked(false)
            }
        } else {
            holder.getView<MaskImageView>(R.id.itemImageView).isChecked = false
            holder.setImageSelectionMode(false)
        }
    }

    fun setSelected(position: Int, isSelected: Boolean, refreshItem: Boolean = true) {
        if (!isChildItem(position)) {
            return
        }
        if (isSelected) {
            selectedImages.add(getItem(position) as Image)
        } else {
            selectedImages.remove(getItem(position))
        }
        if (refreshItem) {
            notifyItemChanged(position, true)
        }
        onSelectChangedCallback?.invoke(selectedImages.size)
    }

【问题讨论】:

  • 这很有趣。请发布您的适配器? RecyclerView 通常不会呈现完整的数据集,只是适合屏幕的数据加上一些额外的数据。另外,我们所说的数据量是多少?
  • @ror 我添加了一些代码,但我认为适配器并不重要。这是真的只有可见的项目会被渲染,但问题是DiffUtil 将比较整个数据列表来计算差异。大约有 1,000~4,000 个项目,由于这个应用程序将在手表设备上运行,所以这个大小已经不小了。

标签: android android-recyclerview android-viewmodel android-mvvm


【解决方案1】:

可能是我弄错了,但看起来你没有使用分页?根据您的数据性质,您可以使用https://developer.android.com/reference/androidx/paging/DataSource.html 的任何后代数据源。然后,您的 DiffUtils 将对更小的数据子集进行操作。您可以将选定的项目(我假设它可能不止一个)偏移到视图模型,将它们保持为状态,甚至动态地将您的原始数据包装到一些具有选择指示的包装器中 - 在数据源本身内部。因此,您的适配器将是纯数据驱动的。

【讨论】:

  • 一些问题: 1、分页不支持加载后修改数据。 2. 因为1,我必须使用另一个变量来保存状态,这意味着我仍然保持数据和视图之间的状态 - 没有数据驱动。 3.除了1之外,即使使用分页,数据也会越来越大。假设用户加载了10个页面,每次提交仍然是整体比较。(即way paging do not support modification
  • 但是受你的启发,我在onDestroy时将选中的项目保存到ViewModle,并在重新创建UI时恢复它。它不是数据驱动模式,但确实解决了问题。我仍在寻找更好的解决方案。
  • @Chenhe 你当然是正确的,但你是在用户可见的页面中执行选择,对吗?这对我来说基本上意味着我可以在新的可分页列表上加载相同的页面作为第一个操作? (developer.android.com/reference/android/arch/paging/…)
  • 这似乎有效。然而,迁移到 Paging 是一个大项目,因为我还有其他功能,例如 Group(selection)、FastScroll、TotalCount 等。也许 Paging 会破坏它们。事实上,我正在构建的是类似于 Google Photos 的东西——一个画廊应用程序。我稍后会尝试这个解决方案。感谢您提出如此独特的想法。
猜你喜欢
  • 2014-06-13
  • 2018-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-20
  • 2010-10-14
  • 2011-12-20
  • 1970-01-01
相关资源
最近更新 更多