When the parent View and child View can slide and slide in the same direction (for example, CoordinatorLayout embedded RecyclerView or Webview), the solution of sliding conflicts needs to rely on the NestedScrolling interface provided by Android.

The NestedScrolling interface is divided into two sections: NestedScrollingParent and NestedScrollingChild.

For ease of description, NestedScrollingParent is NP and NestedScrollingChild is NC.

NestedScrollingChild

Included interfaces:

public interface NestedScrollingChild {
    /** * Sets whether to enable the nested Scroll feature in the current View *@paramEnabled Whether to enable */
    void setNestedScrollingEnabled(boolean enabled);

    /** * Whether the nested Scroll feature is enabled in the current View *@return* /
    boolean isNestedScrollingEnabled(a);

    /** * Initiate nested scroll operation on axes *@paramAxes sliding direction *@returnWhether NestedScrollingParent accepts the slide request */
    boolean startNestedScroll(@ViewCompat.ScrollAxis int axes);

    /** * terminate nested scroll */
    void stopNestedScroll(a);

    /** * Whether the current NestedScrollingParent accepts the slide request *@returnThe return value * /
    boolean hasNestedScrollingParent(a);

    /** * Allocate a preprocessing operation (usually asking NestedScrollingParent if it consumes part of the sliding distance) before it starts sliding@paramDx The total distance of the current slide *@paramThe total y distance of the current slide dy *@paramConsumed distance (this is the output parameter, consumed[0] is the X wheelbase consumed by the pretreatment operation, consumed[1] is the Y wheelbase consumed by the pretreatment operation) *@paramOffsetInWindow This parameter is optional and can be null. Is the output parameter, and the preprocessing operation offsets the position of the current view (offsetInWindow[0] and offsetInWindow[1] are X-axis and Y-axis offsets respectively) *@returnWhether the preprocessing operation consumes part or all of the slip distance */
    boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
                                    @Nullable int[] offsetInWindow);

    /** * Continue to assign the slide after the current View handles the slide. (NestedScrollingParent handles the rest of the slide after it handles the slide itself.) *@paramDxConsumed x slide distance *@paramDyConsumed y slip distance *@paramDxUnconsumed X-axis sliding distance *@paramDyUnconsumed Y-axis sliding distance *@paramOffsetInWindow This parameter is optional and can be null. Is the output parameter, and the preprocessing operation offsets the position of the current view (offsetInWindow[0] and offsetInWindow[1] are X-axis and Y-axis offsets respectively) *@return* /
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
                                 int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);

    /** * Preprocesses the fling event before the current NestedScrollingChild processes it@paramVelocityX x axis velocity *@paramVelocityY y velocity *@returnWhether the pretreatment consumes the fling */
    boolean dispatchNestedPreFling(float velocityX, float velocityY);

    /** * Assign the fling operation *@paramVelocityX x velocity *@paramVelocityY y velocity in the y direction *@paramConsumed Whether the current NestedScrollingChild processes this fling *@returnWhether NestedScrollingParent processed the fling */
    boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
}

Copy the code

NC is the descendant of NP (not necessarily a direct sub-view) and also the requestor of joint sliding. A series of motionEvents generated by sliding are tracked and processed in this View. Generally, this View initiates sliding requests based on tracking analysis of MotionEvent in onTouchEvent. For example, a simplified version of onTouchEvent in RecyclerView:

@Override
public boolean onTouchEvent(MotionEvent e) {
    final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
    final boolean canScrollVertically = mLayout.canScrollVertically();

    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    }
    boolean eventAddedToVelocityTracker = false;

    final MotionEvent vtev = MotionEvent.obtain(e);
    final int action = e.getActionMasked();
    final int actionIndex = e.getActionIndex();

    if (action == MotionEvent.ACTION_DOWN) {
        mNestedOffsets[0] = mNestedOffsets[1] = 0;
    }
    vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);

    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            mScrollPointerId = e.getPointerId(0);
            mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5 f);
            mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5 f);

            int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
            if (canScrollHorizontally) {
                nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
            }
            if (canScrollVertically) {
                nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
            }
            // Initiate a scrolling request
            startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
        } break;

        case MotionEvent.ACTION_MOVE: {
            final int x = (int) (e.getX(index) + 0.5 f);
            final int y = (int) (e.getY(index) + 0.5 f);
            int dx = mLastTouchX - x;
            int dy = mLastTouchY - y;

            // Ask NP if it needs to consume the sliding distance in advance (part or all)
            if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
                // NP consumes part of the slip distance
                dx -= mScrollConsumed[0]; // The X-axis slip distance consumed by NP
                dy -= mScrollConsumed[1]; // The Y slip distance consumed by NP
                vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
                // Updated the nested offsets
                mNestedOffsets[0] += mScrollOffset[0];
                mNestedOffsets[1] += mScrollOffset[1];
            }

            // Analyze whether it needs to slide and the sliding distance it consumes
            if(mScrollState ! = SCROLL_STATE_DRAGGING) {boolean startScroll = false;
                if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                    if (dx > 0) {
                        dx -= mTouchSlop;
                    } else {
                        dx += mTouchSlop;
                    }
                    startScroll = true;
                }
                if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                    if (dy > 0) {
                        dy -= mTouchSlop;
                    } else {
                        dy += mTouchSlop;
                    }
                    startScroll = true;
                }
                if(startScroll) { setScrollState(SCROLL_STATE_DRAGGING); }}if (mScrollState == SCROLL_STATE_DRAGGING) {
                mLastTouchX = x - mScrollOffset[0];
                mLastTouchY = y - mScrollOffset[1];

                // slide inside yourself
                if (scrollByInternal(
                        canScrollHorizontally ? dx : 0,
                        canScrollVertically ? dy : 0,
                        vtev)) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                if(mGapWorker ! =null&& (dx ! =0|| dy ! =0)) {
                    mGapWorker.postFromTraversal(this, dx, dy); }}}break;

        case MotionEvent.ACTION_POINTER_UP: {
            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;
            // Analyze whether the view is not in a fling event.
            if(! ((xvel ! =0|| yvel ! =0) && fling((int) xvel, (int) yvel))) {
                setScrollState(SCROLL_STATE_IDLE);
            }
            resetTouch();
        } break;

        case MotionEvent.ACTION_CANCEL: {
            cancelTouch();
        } break;
    }

    if(! eventAddedToVelocityTracker) { mVelocityTracker.addMovement(vtev); } vtev.recycle();return true;
}
Copy the code

And scrollByInternal simplified version:

boolean scrollByInternal(int x, int y, MotionEvent ev) {
    int unconsumedX = 0, unconsumedY = 0;
    int consumedX = 0, consumedY = 0;

    if(mAdapter ! =null) {
        if(x ! =0) {
            // The X-axis sliding distance consumed by your own sliding
            consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
            // The unconsumed X-axis sliding distance
            unconsumedX = x - consumedX;
        }
        if(y ! =0) {
            // The Y-axis sliding distance consumed by my own sliding
            consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
            // Unconsumed Y slip distanceunconsumedY = y - consumedY; }}// Tell NP to continue consuming the remaining slip distance
    if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
            TYPE_TOUCH)) {
        // Update the last touch co-ords, taking any scroll offset into account
        mLastTouchX -= mScrollOffset[0];
        mLastTouchY -= mScrollOffset[1];
        if(ev ! =null) {
            ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
        }
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];
    }

    // Whether the sliding distance has been completely consumed
    returnconsumedX ! =0|| consumedY ! =0;
}

Copy the code

Therefore, for a sliding operation, the interface call sequence of NC is as follows:

startNestedScroll -> dispatchNestedPreScroll -> dispatchNestedScroll -> stopNestedScroll

The general processing logic can be summarized in the following pseudo-code:

    private int mLastX;
    private int mLastY;
    private int[] mConsumed = new int[2];
    private int[] mOffsetInWindow = new int[2];
    @Override
    void onTouchEvent(MotionEvent event) {
        int eventX = (int) event.getRawX();
        int eventY = (int) event.getRawY();
        int action = event.getAction();
        int deltaX = eventX - mLastX;
        int deltaY = eventY - mLastY;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally()) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically()) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                startNestedScroll(nestedScrollAxis);
                break;
            case MotionEvent.ACTION_MOVE:
                if (dispatchNestedPreScroll(deltaX, deltaY, mConsumed, mOffsetInWindow)) {
                    deltaX -= mConsumed[0];
                    deltaY -= mConsumed[1];
                }
                int internalScrolledX = internalScrollByX(deltaX);
                int internalScrolledY = internalScrollByY(deltaY);
                deltaX -= internalScrolledX;
                deltaY -= internalScrolledY;
                if(deltaX ! =0|| deltaY ! =0) {
                    dispatchNestedScroll(mConsumed[0] + internalScrolledX, mConsumed[1] + internalScrolledY, deltaX, deltaY, mOffsetInWindow);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                stopNestedScroll();
                break;
        }
        mLastX = eventX;
        mLastY = eventY;
    }

    /** ** **@paramDeltaX sliding distance *@returnSlip distance consumed */
    abstract int internalScrollByX(int deltaX);

    /** ** slide in the Y direction *@paramDeltaY sliding distance *@returnSlip distance consumed */
    abstract int internalScrollByY(int deltaY);

    /** * Whether horizontal sliding is supported *@returnWhether */ is supported
    abstract boolean canScrollHorizontally(a);

    /** * Supports vertical sliding *@returnWhether */ is supported
    abstract boolean canScrollVertically(a);
Copy the code

NestedScrollingParent

Contains interfaces:


public interface NestedScrollingParent {

    / * * * to NP sons began to slide request response (NestedScrollingChild. StartNestedScroll) *@paramChild contains the direct child view * of the NP descendant view that initiated the request@paramTarget initiates the request's NP descendant view *@paramAxes sliding direction *@returnWhether to respond to this slide event */
    boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ViewCompat.ScrollAxis int axes);

    /** * A callback to the sliding response (this will happen if onStartNestedScroll returns true) to give NestedScrollingParent time to do the sliding initialization@paramChild contains the direct child view * of the NP descendant view that initiated the request@paramTarget initiates the request's NP descendant view *@paramAxes sliding direction */
    void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ViewCompat.ScrollAxis int axes);

    /** * Terminates the nested scroll callback (NestedScrollingChild calls stopNestedScroll) *@paramTarget initiates the request's NP descendant view */
    void onStopNestedScroll(@NonNull View target);

    /** * Preprocess the slide before NestedScrollingChild processes the slide@paramTarget initiates the request's NP descendant view *@paramDx x sliding distance *@paramDy y distance *@paramBackfill parameter, fill in the sliding distance consumed by this pretreatment */
    void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);

    /** * Process NestedScrollingChild's unconsumed sliding distance *@paramTarget initiates the request's NP descendant view *@paramDxConsumed X-axis sliding distance *@paramDyConsumed y axis sliding distance *@paramDxUnconsumed X-axis sliding distance *@paramDyUnconsumed Y-axis sliding distance */
    void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
                                 int dxUnconsumed, int dyUnconsumed);

    /** * Preprocess the Fling event before NestedScrollingChild *@paramTarget initiates the request's NP descendant view *@paramVelocityX X axis speed *@paramVelocityY Y axis speed *@returnWhether to process the fling */
    boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);

    /** * Process the fling event *@paramTarget initiates the request's NP descendant view *@paramVelocityX X axis speed *@paramVelocityY Y axis speed *@paramConsumed Whether the Fling * has been processed@returnWhether to process the fling */
    boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);


    /** * get slide direction *@returnSliding direction */
    @ViewCompat.ScrollAxis
    int getNestedScrollAxes(a);
}

Copy the code

Interface call order

  1. When processing MotionEvent, NC decides to initiate a slide request and call startNestedScroll
  2. Calling startNestedScroll will traverse the parent view layer by layer and invoke its onStartNestedScroll interface. If true is returned, the view is the NP associated with the nested Scroll and the traversal is interrupted. Return false and continue traversing up to the root view. If traversing to the root view does not find linkage NP, linkage is unavailable for subsequent slides. If found, go to step 3.
  3. NC calls NP’s onNestedScrollAccepted interface.
  4. NP’s onNestedScrollAccepted interface is called to do some sliding initial work.
  5. NC detects that the user interaction has generated a sliding distance and calls NP’s onNestedPreScroll interface.
  6. NP’s onNestedPreScroll interface is called to preprocess the slide, consuming part of the slide distance (or none).
  7. NC processes the remaining slip distance.
  8. If NC has not processed the rest of the slide distance, dispatchNestedScroll is called.
  9. NP’s onNestedScroll is called to decide for itself whether to continue processing the remaining sliding distance.
  10. The slide on the interaction terminates and NC calls stopNestedScroll.
  11. NP onStopNestedScroll is called.