RecyclerView提交新列表/变更排序时自动滚动,如何保留原有滚动位置?
ListAdapter提交排序列表后RecyclerView自动滚动的解决方案
该问题的核心原因:ListAdapter依赖DiffUtil计算新旧列表差异,当列表整体排序发生变化时,若DiffUtil将位置变更识别为「旧位置item删除+新位置item插入」而非「item移动」,或是RecyclerView默认跟随原可见item移动到新位置,就会触发自动滚动。倒序场景下原顶部item移动到列表底部,因此会出现自动滚动到底部的表现。
方案1:保存滚动位置,提交完成后恢复(通用性最高)
该方案不受排序逻辑影响,可以100%恢复到提交前的滚动位置,适用所有排序场景:
- 提交新列表前,从LayoutManager中获取当前可见位置和偏移量
val layoutManager = recyclerView.layoutManager as LinearLayoutManager // 获取第一个可见item的位置 val firstVisiblePos = layoutManager.findFirstVisibleItemPosition() // 获取第一个可见item距离顶部的偏移量 val firstItemOffset = layoutManager.findViewByPosition(firstVisiblePos)?.top ?: 0 - 调用
submitList时,在完成回调中恢复滚动位置
如果排序后需要保留用户正在查看的特定item的可见性,可以根据排序规则换算目标item的新位置,替换上面的adapter.submitList(yourNewSortedList) { // 提交完成后恢复位置 layoutManager.scrollToPositionWithOffset(firstVisiblePos, firstItemOffset) }firstVisiblePos即可。
方案2:优化DiffUtil配置,避免误判item变更
大部分自动滚动问题都和不合理的DiffUtil配置有关,调整配置后可以从根源解决问题:
- 确保
DiffUtil.ItemCallback的areItemsTheSame方法仅判断item的唯一标识(如业务ID),不要用内容作为匹配依据 - 确保
areContentsTheSame方法仅对比和UI展示相关的字段,排序优先级、排序权重这类不影响UI展示的字段不要加入对比 - 不要手动关闭DiffUtil的
detectMoves参数,默认开启状态下DiffUtil会识别item的移动操作,不会将排序识别为批量删除+插入,也就不会触发自动滚动
方案3:临时禁用滚动(可选)
如果不需要排序过程中的动画效果,可以在提交列表前后临时禁用RecyclerView的滚动:
// 提交前禁用滚动 recyclerView.isNestedScrollingEnabled = false recyclerView.setOnTouchListener { _, _ -> true } adapter.submitList(yourNewSortedList) { // 提交完成恢复滚动 recyclerView.isNestedScrollingEnabled = true recyclerView.setOnTouchListener(null) }
内容的提问来源于stack exchange,提问作者Petros Mosoyan
相关产品推荐
相关产品推荐

