AndroidX RecyclerView总结-ItemTouchHelper

本文详细总结了AndroidX RecyclerView中的ItemTouchHelper如何实现Swipe和Drag操作。从基本使用、关键代码到源码探究,分析了触摸事件的托管、ItemTouchHelper的事件处理、SWIPE和DRAG的触发判定以及拖动过程中的滚动机制,揭示了ItemTouchHelper的工作原理。
摘要由CSDN通过智能技术生成

概述

RecyclerView不仅实现在有限窗口显示大数据集,还支持对其中的item视图进行Swipe(轻扫)Drag(拖拽)操作,这可以借助ItemTouchHelper辅助类轻松实现。

基本使用

关键代码:

// 1.创建ItemTouchHelper.Callback,实现回调方法
ItemTouchHelper.Callback callback = new ItemTouchHelper.Callback() {
   
    // 返回允许滑动的方向
    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
   
        // 返回可滑动方向,通过使用一个int,在各个bit位标记来记录。
        // 这里drag支持上下方向,swipe支持左右方向。
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        // 返回设置了标识位的复合int
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    // 允许drag的前提下,当ItemTouchHelper想要将拖动的项目从其旧位置移动到新位置时调用
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
   
        // 获取被拖拽item和目标item的适配器索引(适配器索引是该item对应数据集的索引,getLayoutPosition是当前布局的位置)
        int from = viewHolder.getAdapterPosition();
        int to = target.getAdapterPosition();
        // 交换数据集的数据
        Collections.swap(data, from, to);
        // 通知Adapter更新
        adapter.notifyItemMoved(from, to);
        // 返回true表示item移到了目标位置
        return true;
    }

    // 允许swipe的前提下,当用户滑动ViewHolder触发临界时调用
    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
   
        // 获取滑动的item对应的适配器索引
        int pos = viewHolder.getAdapterPosition();
        // 从数据集移除数据
        data.remove(pos);
        // 通知Adapter更新
        adapter.notifyItemRemoved(pos);
    }
};
// 2.传入ItemTouchHelper.Callback
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
// 3.将touchHelper和recyclerView绑定
touchHelper.attachToRecyclerView(recyclerView);

以上代码三个步骤就可以实现swipe和drag,效果如图:

swipe示例

drag示例

关键思考

我们知道RecyclerView作为ViewGroup,有自己的滑动事件处理,那么ItemTouchHelper是如何进行swipe和drag,而不产生冲突。

ItemTouchHelper如何通过attachToRecyclerView方法附加RecyclerView,就能将触摸事件托管到自己身上执行。

ItemTouchHelper.Callback接口中的onMove和onSwiped是在什么时机回调。

我们注意到上面图示中,Drag操作拖拽item到达边界时,RecyclerView会跟着滚动起来,这是如何调度的。

带着这些问题进入源码,看看ItemTouchHelper大致实现机制,就能知道答案。

源码探究

文中源码基于 ‘androidx.recyclerview:recyclerview:1.1.0’

ItemTouchHelper绑定

首先看attachToRecyclerView方法:
[ItemTouchHelper#attachToRecyclerView]

public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
   
    if (mRecyclerView == recyclerView) {
   
        return; // nothing to do
    }
    // 若绑定过其他RecyclerView,则与旧的解除绑定和清理数据
    if (mRecyclerView != null) {
   
        destroyCallbacks();
    }
    mRecyclerView = recyclerView;
    if (recyclerView != null) {
   
        final Resources resources = recyclerView.getResources();
        mSwipeEscapeVelocity = resources
                .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
        mMaxSwipeVelocity = resources
                .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
        // 设置回调
        setupCallbacks();
    }
}

关键在setupCallbacks方法中:
[ItemTouchHelper#setupCallbacks]

private void setupCallbacks() {
   
    ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
    mSlop = vc.getScaledTouchSlop();
    // 添加到mRecyclerView的mItemDecorations集合中
    mRecyclerView.addItemDecoration(this);
    // 添加到mRecyclerView的mOnItemTouchListeners集合中
    mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
    // 添加到mRecyclerView的mOnChildAttachStateListeners集合中
    mRecyclerView.addOnChildAttachStateChangeListener(this);
    // 创建GestureDetector
    startGestureDetection();
}

该方法中进行各类回调监听注册,这些回调监听是实现swipe和drag的关键。

ItemTouchHelper继承ItemDecoration,ItemDecoration作用是装饰item,通常用来绘制分割线。ItemTouchHelper借助其实现item跟随手指移动。

mOnItemTouchListener注册给RecyclerView后,RecyclerView会将事件回调给它,ItemTouchHelper从而能够拦截事件派发自行处理。

ItemTouchHelper实现OnChildAttachStateChangeListener接口,在该接口的onChildViewDetachedFromWindow方法中处理视图detached时进行释放动作或结束动画、清理视图引用。

startGestureDetection方法中会创建GestureDetector,用于监听触摸事件。当触发onLongPress长按时,判断是否开始drag。

RecyclerView触摸事件托管

接下来看看RecyclerView如何把触摸事件托管给ItemTouchHelper。

onInterceptTouchEvent

[RecyclerView#onInterceptTouchEvent]

public boolean onInterceptTouchEvent(MotionEvent e) {
   
    // ···
    mInterceptingOnItemTouchListener = null;
    // 判断是否有OnItemTouchListener拦截事件
    if (findInterceptingOnItemTouchListener(e)) {
   
        // 若有拦截则取消滚动
        cancelScroll();
        return true;
    }
    // RecyclerView的onInterceptTouchEvent逻辑 ···
}

private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
   
    int action = e.getAction();
    final int listenerCount = mOnItemTouchListeners.size();
    // 依次将事件派发给mOnItemTouchListeners保存的listener
    for (int i = 0; i < listenerCount; i++) {
   
        final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
        if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
   
            // 若有listener拦截事件并且当前事件不是CANCEL,则用mInterceptingOnItemTouchListener保存该listener,结束遍历
            mInterceptingOnItemTouchListener = listener;
            return true;
        }
    }
    return false;
}

RecyclerView在onInterceptTouchEvent方法中处理RecyclerView自身事件拦截逻辑前,会先派发给OnItemTouchListener集合,若有OnItemTouchListener处理则RecyclerView自身不再处理。

onTouchEvent

[RecyclerView#onTouchEvent]

public boolean onTouchEvent(MotionEvent e) {
   
    // ···
    // 判断是否有OnItemTouchListener消费事件
    if (dispatchToOnItemTouchListeners(e)) {
   
        // 若有消费事件则取消滚动
        cancelScroll();
        return true;
    }
    // RecyclerView的onTouchEvent逻辑 ···
}

private boolean dispatchToOnItemTouchListeners(MotionEvent e) {
   
    if (mInterceptingOnItemTouchListener == null) {
   
        // 若在onInterceptTouchEvent时没有OnItemTouchListener拦截事件,那么这里
        // 还会将事件派发给OnItemTouchListener,但是会过滤掉DOWN事件,避免重复派发。
        if (e.getAction() == MotionEvent.ACTION_DOWN) {
   
            return false;
        }
        return findInterceptingOnItemTouchListener(e);
    } else {
   
        // 若有拦截事件的OnItemTouchListener,则直接交给它的onTouchEvent方法
        mInterceptingOnItemTouchListener.onTouchEvent(this, e);
        final int action = e.getAction();
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
   
            // 若是事件序列结束,则清空mInterceptingOnItemTouchListener
            mInterceptingOnItemTouchListener = null;
        }
        return true;
    }
}

RecyclerView在onTouchEvent中处理自身的逻辑前,会先将事件派发给OnItemTouchListener,若有消费事件,则RecyclerView自身不再处理。

requestDisallowInterceptTouchEvent

RecyclerView重写了requestDisallowInterceptTouchEvent方法,在其中也会回调OnItemTouchListener:
[RecyclerView#requestDisallowInterceptTouchEvent]

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
   
    final int listenerCount = mOnItemTouchListeners.size();
    // 依次调用OnItemTouchListeners集合中listener
    for (int i = 0; i < listenerCount; i++) {
   
        final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
        // 在ItemTouchHelper的listener中,若传入不希望拦截事件,那么ItemTouchHelper会释放移动
        listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
    }
    super.requestDisallowInterceptTouchEvent(disallowIntercept);
}

ItemTouchHelper拦截事件处理

RecyclerView在收到触摸事件时,会优先将事件交给OnItemTouchListener,若有事件被消费,则RecyclerView自身不再消费。ItemTouchHelper便是通过OnItemTouchListener来接收事件,触发SWIPE或DRAG。

onInterceptTouchEvent

看看ItemTouchHelper的mOnItemTouchListener实现的对应事件拦截方法。

[OnItemTouchListener#onInterceptTouchEvent]

public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
        @NonNull MotionEvent event) {
   
    // GestureDetector监听输入的事件
    mGestureDetector.onTouchEvent(event);
    if (DEBUG) {
   
        Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
    }
    final int action = event.getActionMasked();
    if (action == MotionEvent.ACTION_DOWN) {
   
        // 若该event是
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值