You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免RecyclerView排序更新列表时自动滚动?

问题描述

我有一个展示图书列表的常规RecyclerView,支持按书名升序或降序排序。通过DiffUtil将排序后的列表提交给适配器时,列表项可正常交换位置,但RecyclerView会自动滚动:当前视图顶部显示的图书(非列表首项)会保持在顶部,导致滚动位置变化。如何在排序更新列表时保持RecyclerView的滚动位置不变?

补充代码示例

在Fragment中收集包含已排序列表的StateFlow:

...
stateflow.collect{
    // ...
    adapter.differ.submitList(it.borrowedItems)
    // ...
}

适配器中的DiffUtil实现:

private val differCallback = object : DiffUtil.ItemCallback<BorrowedItem>() {
        override fun areItemsTheSame(oldItem: BorrowedItem, newItem: BorrowedItem): Boolean {
            return oldItem.itemId == newItem.itemId
        }

        override fun areContentsTheSame(oldItem: BorrowedItem, newItem: BorrowedItem): Boolean {
            return oldItem == newItem
        }
}

val differ = AsyncListDiffer(this, differCallback)
解决方法

核心思路是:提交新列表前以固定item为锚点记录当前滚动位置,列表更新完成后恢复到该锚点的位置。

方法一:锚点item记录与恢复

这是推荐的方案,既能保留DiffUtil的高效更新和动画,又能稳定维持滚动位置。

  1. 提交前获取锚点信息
    找到当前RecyclerView中第一个完全可见的item,记录它的唯一ID:
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisiblePos = layoutManager.findFirstCompletelyVisibleItemPosition()
val anchorItemId = if (firstVisiblePos != RecyclerView.NO_POSITION) {
    adapter.differ.currentList[firstVisiblePos].itemId
} else {
    null
}
  1. 提交后恢复位置
    利用submitList的回调,在列表更新完成后找到锚点item的新位置,滚动到该位置并保持顶部对齐:
adapter.differ.submitList(it.borrowedItems) {
    anchorItemId?.let { id ->
        val newPos = it.borrowedItems.indexOfFirst { item -> item.itemId == id }
        if (newPos != -1) {
            layoutManager.scrollToPositionWithOffset(newPos, 0)
        }
    }
}

方法二:禁用自动滚动(不推荐)

如果不需要DiffUtil的局部更新和动画效果,可以通过自定义观察者跳过默认滚动调整,但会触发全量更新:

val customDiffer = object : AsyncListDiffer(this, differCallback) {
    override fun getAdapterDataObserver(): AdapterDataObserver {
        return object : AdapterDataObserver() {
            override fun onChanged() {
                // 不调用父类方法,避免默认滚动调整,直接全量通知更新
                notifyDataSetChanged()
            }
        }
    }
}

注意:此方法会丢失DiffUtil的性能优势,仅适合简单列表场景。

关键注意事项

  • 确保areItemsTheSame方法正确实现,item的itemId必须唯一且稳定,否则无法准确匹配锚点item。
  • 使用scrollToPositionWithOffset而非scrollToPosition,可以保证锚点item精确显示在视图顶部(偏移量设为0)。

内容的提问来源于stack exchange,提问作者sjohansson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 18:27:52