使用ItemTouchHelper拖拽RecyclerView项时列表滚动至顶部的问题
嘿,看到你碰到的这个问题了——拖拽RecyclerView里的项时列表滚回顶部,还精准排查到是notifyItemMoved触发的,而且RecyclerView还嵌套在ViewPager里。结合你贴的代码,我帮你分析下原因和修复办法:
问题根源
你已经定位到核心触发点了,这个问题在ListAdapter配合ItemTouchHelper时挺常见的:一方面,手动调用notifyItemMoved和ListAdapter依赖的DiffUtil自动更新机制容易出现状态冲突;另一方面,默认的LinearLayoutManager在收到item移动通知时,可能因为ViewPager的页面缓存/重建逻辑,误触发滚动到顶部的行为。
具体修复方案
方案1:改用ListAdapter的submitList更新(推荐)
既然你用的是ListAdapter,它本来就设计成通过提交新列表让DiffUtil自动计算差异,手动调用notifyItemMoved反而容易导致状态不一致。修改onMove方法如下:
override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { val fromPos = viewHolder.adapterPosition val toPos = target.adapterPosition // 复制原列表为可变列表,避免直接修改原数据(ListAdapter要求数据源不可变) val newAccountList = _accountList.toMutableList() // 交换位置 Collections.swap(newAccountList, fromPos, toPos) // 提交新列表给Adapter,DiffUtil会自动处理item移动和动画 accountListAdapter.submitList(newAccountList) // 更新你的数据源 _accountList = newAccountList return true }
这种方式完全贴合ListAdapter的设计,能避免手动notify带来的滚动异常,动画也会更流畅。
方案2:手动保存并恢复滚动位置
如果不想改动数据源的更新方式,可以在notifyItemMoved前后记录并恢复RecyclerView的滚动位置:
override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { val fromPos = viewHolder.adapterPosition val toPos = target.adapterPosition val layoutManager = recyclerView.layoutManager as LinearLayoutManager // 保存当前滚动的位置和偏移量 val firstVisiblePos = layoutManager.findFirstVisibleItemPosition() val topOffset = layoutManager.findViewByPosition(firstVisiblePos)?.top ?: 0 // 交换数据并通知Adapter Collections.swap(_accountList, fromPos, toPos) accountListAdapter.notifyItemMoved(fromPos, toPos) // 强制恢复之前的滚动位置 layoutManager.scrollToPositionWithOffset(firstVisiblePos, topOffset) return true }
这个方法直接拦截了滚动回顶部的行为,适合快速修复场景。
方案3:优化ViewPager的页面缓存
因为RecyclerView在ViewPager里,ViewPager默认的预加载页数可能导致当前页面的RecyclerView被销毁重建,间接引发滚动异常。给ViewPager设置足够的预加载页数:
// 在初始化ViewPager的地方添加 viewPager.offscreenPageLimit = 2 // 至少包含当前页面,根据你的页面数量调整
这样能保证当前页面的RecyclerView状态不会被轻易销毁,减少奇怪的滚动问题。
额外提醒
你已经设置了accountListAdapter.setHasStableIds(true),并且getItemId返回的是唯一的accountId,这部分做得很对,一定要保留,它能帮助RecyclerView更准确地跟踪item状态。
内容的提问来源于stack exchange,提问作者MakinTosH

