开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
导航:Android Measure流程(一)juejin.cn/post/716850…
Android Measure流程(二)juejin.cn/post/716924…
Android Measure流程(三)juejin.cn/post/716948…
NestedScrollView
我们都知道NestedScrollView是处理嵌套滑动的,那么首先我们先看他的ontouchevent对ACTION_MOVE是如何处理的。
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
}
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
deltaY -= releaseVerticalGlow(deltaY, ev.getX(activePointerIndex));
//mTouchSlop的值表示:当滑动距离大于这个值时才开始移动(主要因为mIsBeingDragged=true),deltaY正数代表向下滑动,负数向上滑
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
//消耗掉最小滑动距离
if (deltaY > 0) {
deltaY -= mTouchSlop;
} else {
deltaY += mTouchSlop;
}
}
if (mIsBeingDragged) {
//分发给parent的onNestedPreScroll(parent指的是离当前view最近的支持嵌套滑动的paret(也就是onStartNestedScroll返回true的parent,寻找parent的过程是在startNestedScroll中进行的,他是发生在ACTION_DOWN事件中,发生位置是和RecyclerView一样的,都是DOWN事件,所以在每一组事件中我们都可以在onStartNestedScroll控制是否接收此次嵌套滑动,也只能在DWON事件时处理,在其他事件中处理是无效的。每次滑动完成,在stopNestedScroll中会将在startNestedScroll寻找到的parent置为null,下次事件需要重新寻找)
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
ViewCompat.TYPE_TOUCH)) {
deltaY -= mScrollConsumed[1];//deltaY现在是剩余可滑动距离
mNestedYOffset += mScrollOffset[1];
}
// Scroll to follow the motion event
mLastMotionY = y - mScrollOffset[1];
final int oldY = getScrollY();
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS
|| (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
//内部滑动
boolean clearVelocityTracker =
overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0,
0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
//scrolledDeltaY已经滑动的距离
final int scrolledDeltaY = getScrollY() - oldY;
//unconsumedY内部滑动后剩余可滑动距离
final int unconsumedY = deltaY - scrolledDeltaY;
mScrollConsumed[1] = 0;
dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
ViewCompat.TYPE_TOUCH, mScrollConsumed);
mLastMotionY -= mScrollOffset[1];
mNestedYOffset += mScrollOffset[1];
if (canOverscroll) {
deltaY -= mScrollConsumed[1];
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
EdgeEffectCompat.onPullDistance(mEdgeGlowTop,
(float) -deltaY / getHeight(),
ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
EdgeEffectCompat.onPullDistance(mEdgeGlowBottom,
(float) deltaY / getHeight(),
1.f - ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}
if (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished()) {
ViewCompat.postInvalidateOnAnimation(this);
clearVelocityTracker = false;
}
}
if (clearVelocityTracker) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
}
break;
不得不说和RecyclerView对move事件的处理很想,我们再看一下RecyclerView是如何处理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 (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;
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
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];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
处理过程还是很像的
我们通过看NestedScrolliView对Move事件的处理,他把横向距离一直设置为0,他是不支持横向滑动的。
我们知道不是on开头的nested就两个,一个是start一个是stop,我们知道start是在DOWN事件中用于寻找onStartNestedScroll返回true的父类,而stop用于将这个父类置为null。那么stop是在何处调用的呢,我们在onTouchEvent的up事件里面也没有找到。
继续向上找,在dispatchTouchEvent中,会判断当前的事件是否为DOWN,是的话就是执行stopNestedScroll。
up事件会执行disptchPreFling->dispatchNestedFling->最后才是自己fling。所以fling时和其他嵌套滑动的顺序不一样。