NestedScrollView(一)

354 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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时和其他嵌套滑动的顺序不一样。