RecyclerView横向布局拖拽时Item被回收,如何规避该问题?
解决横向RecyclerView拖拽时Item被意外回收的问题
我明白你遇到的困扰了——长按横向RecyclerView的Item拖拽到屏幕边缘时,Item会突然消失,排查后发现是RecyclerView的回收机制触发了removeDetachedView,进而调用了ItemTouchHelper的clearView方法。这个问题的核心是:默认的RecyclerView布局管理会把拖出可见区域的Item判定为“不再需要”,从而触发回收,但此时ItemTouchHelper还在处理拖拽操作,两者的逻辑产生了冲突。
下面给你一套可行的解决方案,分步骤来实现:
1. 标记正在拖拽的ViewHolder
首先修改你的ItemTouchHelper.Callback,添加变量记录当前处于拖拽状态的ViewHolder,让后续布局管理器能识别出哪个Item不能被回收:
class DragCallback extends ItemTouchHelper.Callback { private RecyclerView.ViewHolder draggingViewHolder; @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { // 横向RecyclerView只需要左右拖拽的flag,上下拖拽可以去掉 int dragFlags = ItemTouchHelper.START | ItemTouchHelper.END; return makeMovementFlags(dragFlags, ItemTouchHelper.ACTION_STATE_IDLE); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { int oldPosition = viewHolder.getAdapterPosition(); int newPosition = target.getAdapterPosition(); // 这里一定要更新Adapter的数据源并通知刷新,否则拖拽后位置会错乱 Collections.swap(mAdapter.getDataList(), oldPosition, newPosition); mAdapter.notifyItemMoved(oldPosition, newPosition); Log.d(getClass().getSimpleName(), String.format("View moved from %d to %d", oldPosition, newPosition)); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { // 不需要侧滑功能,留空即可 } @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); // 标记开始/结束拖拽的ViewHolder if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { draggingViewHolder = viewHolder; } else { draggingViewHolder = null; } } // 提供方法让布局管理器获取当前拖拽的ViewHolder public RecyclerView.ViewHolder getDraggingViewHolder() { return draggingViewHolder; } }
2. 自定义LayoutManager阻止回收拖拽中的Item
接下来自定义一个LinearLayoutManager,重写两个关键方法来规避回收逻辑:
getExtraLayoutSpace:增加额外的布局空间,避免拖拽时Item被判定为超出布局范围shouldRecycleView:判断如果是正在拖拽的Item,就跳过回收操作
public class NoRecycleDragLinearLayoutManager extends LinearLayoutManager { private DragCallback dragCallback; public NoRecycleDragLinearLayoutManager(Context context, int orientation, boolean reverseLayout, DragCallback dragCallback) { super(context, orientation, reverseLayout); this.dragCallback = dragCallback; } // 增加一个屏幕宽度的额外布局空间,防止Item拖拽时被判定为超出布局 @Override protected int getExtraLayoutSpace(RecyclerView.State state) { return super.getExtraLayoutSpace(state) + getWidth(); } // 阻止回收正在拖拽的ViewHolder @Override public boolean shouldRecycleView(View child) { RecyclerView.ViewHolder holder = findViewHolderForAdapterPosition(getPosition(child)); if (holder != null && holder == dragCallback.getDraggingViewHolder()) { return false; } return super.shouldRecycleView(child); } }
3. 替换原有的LayoutManager并绑定
最后把原来的LinearLayoutManager替换成我们自定义的,同时将DragCallback传递进去完成绑定:
// 创建自定义的拖拽回调 DragCallback dragCallback = new DragCallback(); // 设置自定义布局管理器 mView.recycler.setLayoutManager(new NoRecycleDragLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false, dragCallback)); mView.recycler.setAdapter(mAdapter = new SimpleAdapter()); // 绑定ItemTouchHelper ItemTouchHelper touchHelper = new ItemTouchHelper(dragCallback); touchHelper.attachToRecyclerView(mView.recycler);
额外注意点
- 一定要在
onMove方法中更新Adapter的数据源并调用notifyItemMoved,否则拖拽后RecyclerView的Item位置会出现错乱,这是很多人容易忽略的细节。 - 如果你的Item数量不多,也可以临时设置
mView.recycler.setItemViewCacheSize(mAdapter.getItemCount())来强制缓存所有Item,但这种方法会占用更多内存,不如自定义LayoutManager优雅。
内容的提问来源于stack exchange,提问作者mroczis
相关产品推荐
相关产品推荐

