ItemTouchHelper是官方提供的增强RecyclerView的方案,提供侧滑删除和拖动位置的功能,使用上不算特别简单,需要自己实现Callback回调接口。本文略过使用方法主要从整体流程上分析ItemTouchHelper的实现原理。
ItemTouchHelper是通过先行拦截RecyclerView的MotionEvent实现的,ItemTouchHelper先判断是否消耗事件,不消耗才由RecyclerView继续处理,入口如下:
//都是RecyclerView的方法
public boolean onInterceptTouchEvent(MotionEvent e) {
mInterceptingOnItemTouchListener = null;
if (findInterceptingOnItemTouchListener(e)) {
cancelScroll();
return true;
}
......
}
public boolean onTouchEvent(MotionEvent e) {
if (dispatchToOnItemTouchListeners(e)) {
cancelScroll();
return true;
}
......
}
onInterceptTouchEvent判断是否消耗事件,onTouchEvent处理消耗后的具体操作。熟悉安卓view事件体系的应该知道,onInterceptTouchEvent方法只有在目前尚未知道由哪个view消耗事件的时候才会调用,一旦有某个view消耗了事件(return true),那么下次再有事件到达的时候(down除外),就会默认交给那个view,不再依次调用每个view的onInterceptTouchEvent来询问。所以ItemTouchHelper会在onInterceptTouchEvent中判断是否要消耗该事件。
ItemTouchHelper需要对两种情况响应:长按和侧滑,其中侧滑使用checkSelectForSwipe判断,长按交给了GestureDetectorCompat(官方的一个手势识别类),之后调用select方法表示选中了该view。
识别之后就是动画,选中的view需要跟随手指移动,这点容易想到,只需要在onTouchEvent的move事件中更新位置,然后调用mRecyclerView.invalidate()。此处问题在于,为什么重绘可以改变位置呢?因为ItenTouchHelper很鸡贼地继承了ItemDecoration,所以RecyclerView重绘的时候也会调用ItemTouchHelper的onDraw方法,在其中使用setTranslationX/Y更新了选中view的位置。
再然后就是拖动时和其他view交换位置和侧滑删除的动画如何实现。先说交换位置,当ITemTouchHelper检测到需要交换时会回调用户自己实现的onMove方法,在检测到侧滑删除的时候会回调用户自己实现的onSwiped方法。所以现实是,ItemTouchHelper并没有实现这两种动画,之所以能看到动画效果是notifyItemMoved和notifyItemRemoved的功劳,这之后的过程在RecyclerView源码分析中跟踪过。所以交换位置和侧滑删除的动画是RecyclerView实现的,和ItemTouchHelper无关。
最后是松开手指后view回到应该在的位置。在收到UP事件时会根据信息计算view该回到的地方,new一个RecoverAnimation加入到mRecoverAnimations容器中。RecoverAnimation里面实际是一个ValueAnimator,但是如下所示
mValueAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setFraction(animation.getAnimatedFraction());
}
});
public void setFraction(float fraction) {
mFraction = fraction;
}
并没有什么动作,那么动画究竟是怎么实现的?在ItemTouchHelper的onDraw方法中会遍历mRecoverAnimations容器,取出mFraction值,再对view执行setTranslationX/Y,官方注释称这样可以减少丢帧,个人认为也是给自定义动画留一点空间。那么RecoverAnimation又是什么时候从mRecoverAnimations中移除的呢?不是动画结束之后,而是view被从父view删除时的RecyclerView会回调ItemTouchHelper的onChildViewDetachedFromWindow删除RecoverAnimation,没错,又是RecyclerView干的。
最后还有一些细节需要处理,比如被选中的view能够挡住其他view,这是设置elevation+1实现的,当然动画结束之后要设回去。还有当拖到屏幕边缘需要调用mRecyclerView.scrollBy滚动起来。
以上是ItemTouchHelper的默认效果,如果我们需要一些自定义的效果可以修改它的实现。例如需要侧滑的渐变,可以在继承ItemTouchHelper.Callback的时候重写onChildDraw方法:
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(value);
}
}
但是别忘了,因为RecyclerView是复用view的,所以最后还需要恢复Alpha值,重写clearView即可,clearView会在动画结束时调用,无论是删除还是回归原位都会执行动画,并在结束时调用clearView,所以Alpha就恢复了
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setAlpha(1);
}
偷一下懒,本文没有列举源码逐句分析,因为分析完RecyclerView本身的源码已经能对细节有大概的把握,此时更重要的是对实现原理的理解。阅读前对于ItemTouchHelper的效果很好奇,阅读后才恍然大悟,起初感到很困惑的地方,原来都是借助的RecyclerView的默认实现。