使用ItemTouchHelper和ListAdapter实现RecyclerView拖拽时,拖动超过一个位置后手势被强制释放的问题排查
我之前也踩过类似的坑,结合你的代码逻辑来看,核心问题大概率出在拖拽过程中重复的列表更新导致RecyclerView状态混乱,具体分析和解决方案如下:
问题根源:拖拽时同时触发了adapter本地更新与ViewModel驱动的submitList
看你的代码链路:
- 你已经在
PlayViewSoundItemAdapter的onItemMoved里,同步操作了本地的tempItems列表,并且调用notifyItemMoved更新UI——这部分是正确的,本来就是为了避开ListAdaptersubmitList的异步特性。 - 但你同时在Fragment的
onMove回调里调用了soundViewModel.moveItemInTempList(from, to):如果这个ViewModel方法会修改LiveData中的列表,且Fragment里有观察者监听该LiveData并调用adapter.submitList(),那麻烦就来了:
拖拽过程中每移动一次位置,就会触发一次submitList,而submitList依赖异步的DiffUtil计算,会导致RecyclerView重新绑定视图。此时ItemTouchHelper正在跟踪拖拽的ViewHolder,视图的突然刷新会让它丢失对拖拽状态的跟踪,从而强制结束拖拽手势。
解决方案:让adapter本地维护拖拽状态,仅在拖拽结束后同步给ViewModel
既然你已经用tempItems同步维护拖拽过程中的列表状态,完全不需要在拖拽过程中实时通知ViewModel更新——只需要在拖拽完成后把最终有序列表传给ViewModel即可。具体修改步骤:
移除拖拽过程中的ViewModel实时更新
修改Fragment中initRecyclerView里的onMove回调,去掉ViewModel的调用:onMove = { _, _ -> // 拖拽过程中不需要通知ViewModel,避免触发submitList打断手势 // soundViewModel.moveItemInTempList(from, to) }确保adapter的tempItems是拖拽过程中唯一的数据源
你的submitList方法已经正确同步了tempItems,拖拽时的Collections.swap(tempItems, from, to)+notifyItemMoved(from, to)足以让RecyclerView正确更新位置,不需要额外的ViewModel介入。验证ViewModel的列表更新逻辑
检查soundViewModel.moveItemInTempList方法,如果它会创建新的列表对象并更新LiveData,那之前的每一次拖拽都会触发submitList,这就是手势中断的直接原因。现在只保留onOrderChanged里的soundViewModel.finalizeMove(finalList),让ViewModel在拖拽完成后再处理最终的列表排序。
测试验证
修改后再尝试拖拽:你会发现可以正常拖动item到任意位置,不会再出现拖拽到第二个位置就被强制释放的情况。
本质上,这个问题就是异步的submitList和同步的拖拽手势状态冲突,只要让拖拽过程完全由adapter本地同步处理,仅在结束时同步最终状态给ViewModel,就能解决这个问题。
内容来源于stack exchange

