ItemTouchHelper的使用
ItemTouchHelper可以解释为:Item点击的帮助类,它可以判断帮助我们快速的实现侧滑Swiped、长按拖动Move的效果。如下图的效果,用ItemTouchHelper就可以很简单的实现。
1.实现ItemTouchHelper.CallBack接口这个接口我们可以决定是否可以侧滑和拖动,以及方向,并知道侧滑、拖动的执行时的方法回调。
class DefaultItemTouchHelper(private var itemTouchStatus: ItemTouchStatus) :
ItemTouchHelper.Callback() {
/**
* dragFlags:拖动支持的方向
* swipeFlags: 滑动支持的方向
*/
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
// 通过makeMovementFlags确定拖动、侧滑的方向
return makeMovementFlags(dragFlags, swipeFlags)
}
// 长按拖动
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return itemTouchStatus.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
}
// 侧滑
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
itemTouchStatus.onItemRemove(viewHolder.adapterPosition)
}
}
在拖动、侧滑对Adapter进行数据更新
public class RvAdapter extends RecyclerView.Adapter implements ItemTouchStatus {
// ....省略Adapter的View创建和数据绑定
// 侧滑删除
@Override
public void onItemRemove(int position) {
data.remove(position);
notifyItemRemoved(position);
}
//长按拖动数据交换
@Override
public boolean onItemMove(int position, int targetPosition) {
Collections.swap(data, position, targetPosition);
notifyItemMoved(position, targetPosition);return true;
}
}
[Activity]将Rv绑到ItemTouchHelper中
val itemTouchHelper = ItemTouchHelper(DefaultItemTouchHelper(adapter!!))
itemTouchHelper.attachToRecyclerView(recyclerView)
实现此功能可以概括为三步:
- 实现ItemTouchHelper.CallBack接口
- 在ItemTouchHelper实例化时将CallBack传给实例
- 通过ItemTouchHelper. attachToRecyclerView将RecyclerView绑定到ItemTouchHelper中。
ItemTouchHelper源码学习
带着问题看源码:
- 为什么RecyclerView的Item在侧滑的时候会调用ItemTouchHelper.CallBack.onSwiped方法?
- 为什么RecyclerView的Item在长按拖动的时候会调用ItemTouchHelper.CallBack.onMoved方法?
- 为什么只通过一行代码attachToRecyclerView绑定RecyclerView后就可以操作RecyclerView的监听事件?
ItemTouchHelper的绑定
先看看ItemTouchHelper的构造函数和attachToRecyclerView [ItemTouchHelper#attachToRecyclerView]
// 接口实例
public ItemTouchHelper(@NonNull Callback callback) {
mCallback = callback;
}
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();
}
}
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mRecyclerView.addItemDecoration(this);
// 这个监听很重要,可以截取RecylerView的手势事件
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
startGestureDetection();
}
我们在使用ItemTouchHelper时,也就是用到这个类中的两个方法, 一构造函数必须传一个CallBack接口的实例。二是attachToRecyclerView方法会给传来的RecyclerView设置监听,其中设置了OnItemTouchListener
监听可以拦截RecyclerView的手势事件,从而使ItemTouchHelper可以拦截RecylerView的事件进行处理。startGestureDetection()
方法创建了GestureDetection用来识别长按手势。
RecyclerView的事件托管
onInterceptTouchEvent[RecyclerView.onInterceptTouchEvent]
// 是否拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutSuppressed) {
// When layout is suppressed, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
// 当前处理的OnItemTouchListener,重新设置为null
mInterceptingOnItemTouchListener = null;
// 先检查是否设置了OnItemTouchListener,那就事件先交给OnItemTouchListener处理
if (findInterceptingOnItemTouchListener(e)) {
// 取消滑动
cancelScroll();
return true;
}
// RecylerView自身处理拦截事件
//....
}
private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
int action = e.getAction();
// 遍历onItemTouchListener集合
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
//执行监听实例的onInterceptTouchEvent方法,如果返回ture和手势不是取消事件,则设置当前处理的OnItemTouchListener和返回Ture拦截事件。
if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mInterceptingOnItemTouchListener = listener;
return true;
}
}
return false;
}
RecyclerView在执行拦截事件onInterceptTouchEvent
会先遍历OnItemTouchListener集合,查找在OnItemTouchListener.onInterceptTouchEvent
中返回true的OnItemTouchListener,并把它设置为当前处理的onItemToucheListener
,返回Ture拦截事件且不执行RecyclerView本身处理的onInterceptTouchEvent代码。
onTouchEvent[RecyclerView.onToucheEvent]
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutSuppressed || mIgnoreMotionEventTillDown) {
return false;
}
// 如果有当前处理的OnItemTouchListener
if (dispatchToOnItemTouchListeners(e)) {
cancelScroll();
return true;
}
//....RecyclerView自身对onTouchEvent方法的处理
}
private boolean dispatchToOnItemTouchListeners(MotionEvent e) {
if (mInterceptingOnItemTouchListener == null) {
// 手势开始时,没有要处理的OnItemTouchListener则返回false
if (e.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}
// 手势在其他事件时,在去询问一下是否拦截,防止中途变更未及时更新
return findInterceptingOnItemTouchListener(e);
} else {
// 调用OnItemTouchListener的.onTouchEvent()方法