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

使用ItemTouchHelper拖拽RecyclerView项时列表滚动至顶部的问题

解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 08:57:37