转载自琼珶和予
RecyclerView 源码分析(二)
RecyclerView的滑动机制
RecyclerView作为一个列表View,天生就可以滑动。作为一个使用者,我们可以不去了解它是怎么进行滑动,但是我们作为一个学习源码的人,必须得知道RecyclerView的滑动机制,所以,我们今天来看看RecyclerView滑动部分的代码。
本文参考资料:
Android 源码分析 - 嵌套滑动机制的实现原理
深入 RecyclerView 源码探究三:绘制和滑动
同时,从RecyclerView的类结构上来看,我们知道RecyclerView实现了NestedScrollingChild接口,所以RecyclerView也是一个可以产生滑动事件的View。我相信大家都有用过CoordinatorLayout和RecyclerView这个组合,这其中原理的也是嵌套滑动。本文在介绍普通滑动中,可能会涉及到嵌套滑动的知识,所以在阅读本文时,需要大家掌握嵌套滑动的机制,具体可以参考我上面的文章:Android 源码分析 - 嵌套滑动机制的实现原理,此文专门从RecyclerView的角度上来理解嵌套滑动的机制。
本文打算从如下几个方面来分析RecyclerView:
- 正常的TouchEvent
- 嵌套滑动(穿插着文章各个地方,不会专门的讲解)
- 多指滑动
- fling滑动
1. 传统事件
现在,我们正式分析源码,首先我们来看看onTouchEvent方法,来看看它为我们做了那些事情:
@Override
public boolean onTouchEvent(MotionEvent e) {
// ······
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
// ······
switch (action) {
case MotionEvent.ACTION_DOWN: {
// ······
} break;
case MotionEvent.ACTION_POINTER_DOWN: {
// ······
} break;
case MotionEvent.ACTION_MOVE: {
// ······
} break;
case MotionEvent.ACTION_POINTER_UP: {
// ······
} break;
case MotionEvent.ACTION_UP: {
// ······
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break;
}
// ······
return true;
}
如上就是RecyclerView的onTouchEvent方法,我大量的简化了这个方法,先让大家对它的结构有一个了解。
其中ACTION_DOWN、ACTION_MOVE、ACTION_UP和ACTION_CANCEL这几个事件,我相信各位同学都比较熟悉,这是View最基本的事件。
可能有人对ACTION_POINTER_DOWN和ACTION_POINTER_UP事件比较陌生,这两个事件就跟多指滑动有关,也是本文重点分析之一。
好了,我们现在开始正式分析源码。在分析源码之前,我先将上面的代码做一个简单的概述。
- 如果当前的mActiveOnItemTouchListener需要消耗当前事件,那么优先交给它处理。
- 如果mActiveOnItemTouchListener不消耗当前事件,那么就走正常的事件分发机制。这里面有很多的细节,稍后我会详细的介绍。
关于第一步,这里不用我来解释,它就是一个Listener的回调,非常的简单,我们重点的在于分析第二步。
(1) Down 事件
我们先来看看这部分的代码吧。
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
这里主要是做了两件事。
- 记录下Down事件的x、y坐标。
- 调用startNestedScroll方法,询问父View是否处理事件。
Down事件还是比较简单,通常来说就一些初始化的事情。
接下来,我们来看看重头戏–move事件
(2) Move事件
我们先来看看这部分的代码:
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id "
+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if