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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:31:08