从哪里找RecyclerView滑动事件处理?
我们都知道RecyclerView是一个可以滑动的ViewGroup,而且还是一个实现
NestedScrollingChild2、3
可以嵌套滑动ViewGroup。那么我们的手指在手机点击滑动时,RecyclerView
究竟是怎么做的呢?
由如下图的ViewGroup事件分发机制可以得出,ViewGroup
手势事件只能在三个方法中:1.在分发事件的方法dispatchTouchEvent
;2.是在拦截事件:onInterceprTouchEvent
;3.在onTouchEvent
方法中。第一个dispatchTouchEvent
RecyclerView并没有重写,而是复用了ViewGroup本省的dispatchTouchEvent方法,所以并没有在dispatchTouchEvent
处理手势事件。第二个onInterceprTouchEvent
拦截方法也只是在Down
和Up
做一些嵌套滑动开始结束查询操作,在MOVE
事件并没有什么事情。所以第三个onTouchEvent
就是RecyclerView手势滑动事件的方法。
onTouchEvent方法处理
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutSuppressed || mIgnoreMotionEventTillDown) {
return false;
}
// RecyclerView提供了一个OnItemTouchListener接口,可以让你自己重写onInterceptTouchEvent,onTouchEvent,
//onRequestDisallowInterceptTouchEvent方法,那么这时就会调用你自定义接口onTouchEvent方法
if (dispatchToOnItemTouchListeners(e)) {
cancelScroll();
return true;
}
if (mLayout == null) {
return false;
}
//向LayoutManager询问是哪边可滑动
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
// 这个根据fing力度算可滑动距离
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
boolean eventAddedToVelocityTracker = false;
final int action = e.getActionMasked();
final int actionIndex = e.getActionIndex();
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsets[0] = mNestedOffsets[1] = 0;
}
final MotionEvent vtev = MotionEvent.obtain(e);
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
//手指点击屏幕时
case MotionEvent.ACTION_DOWN: {
//跟新 id和开始点击位置
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
// 设置滑动方向的变量,查询是否有配合滑动的父View
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;
// 多指按下
case MotionEvent.ACTION_POINTER_DOWN: {
// 跟新最后手指的 id和初始位置
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
}
break;
// 手指在屏幕滑动
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 (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally) {
if (dx > 0) {
dx = Math.max(0, dx - mTouchSlop);
} else {
dx = Math.min(0, dx + mTouchSlop);
}
if (dx != 0) {
startScroll = true;
}
}
if (canScrollVertically) {
if (dy > 0) {
dy = Math.max(0, dy - mTouchSlop);
} else {
dy = Math.min(0, dy + mTouchSlop);
}
if (dy != 0) {
startScroll = true;
}
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// 先给配合嵌套父view滑动
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
// 手指滑动的距离减去父View消耗的距离
dx -= mReusableIntPair[0];
dy -= mReusableIntPair[1];
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
// Scroll has initiated, prevent parents from intercepting
getParent().requestDisallowInterceptTouchEvent(true);
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
// recylcerView将手指滑动的距离自己进行消耗滑动
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
// 尝试拿到ViewHolder
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
case MotionEvent.ACTION_POINTER_UP: {
// 跟新id和初始位置
onPointerUp(e);
}
break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
// 根据速度算出距离
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
// 开始fling距离
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetScroll();
}
break;
case MotionEvent.ACTION_CANCEL: {
cancelScroll();
}
break;
}
if (!eventAddedToVelocityTracker) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
在研究onTouchEvent()
方法执行过程中,先不深究每个方法的具体实现,而是整个流程的大致做了什么。
-
手指按下
: 更新触摸id和初始位置,与查询是否有父View配合嵌套滑动。 -
多指按下
:根据最后的手指跟新触摸id和初始位置。 -
手指滑动
: 得到手指滑动的距离,然后判断这个距离是否达到滑动大小。将手指滑动距离先交给父View消耗dispatchNestedPreScroll
,如果父View消耗了,则手指滑动的距离减去父view消耗的距离,再将滑动的距离交给RV滑动消耗scrollByInternal
,RV消耗完,再询问父View是否要消耗。并且尝试拿到ViewHoldermGapWorker.postFromTraversal
。 -
多指离开
:跟新触摸id和初始位置。 -
单指离开
: 根据Fling的力度算出可滑动的距离,然后调用fling
方法进行滑动。
这里有三个比较重要方法:分别是MOVE
时的RV滑动方法scrollByInternal
,获取ViewHolder的mGapWorker.postFromTraversal
。最后就是执行UP
时的fling
方法。
scrollByInternal
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
int unconsumedX = 0;
int unconsumedY = 0;
int consumedX = 0;
int consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// rv滑动的方法
scrollStep(x, y, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
//询问父view是否要消耗
dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
type, mReusableIntPair);
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];
// 父View是否有消耗
boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
}
considerReleasingGlowsOnScroll(x, y);
}
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}
if (!awakenScrollBars()) {
invalidate();
}
return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
fillRemainingScrollValues(mState);
int consumedX = 0;
int consumedY = 0;
// 调用LayoutManager的scrollHorizontallyBy方法来给RV滑动
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
TraceCompat.endSection();
repositionShadowingViews();
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
scrollByInternal 方法先是调用LayoutManager让RV滑动消耗距离,然后再将剩余的距离询问父View是否需要消耗。
网友评论