1, the preface

For Android, sliding is essential; It’s not complicated, you know in onTouchEvent, you just let it slide and it’s done, it’s complicated, it’s nested; In this series, the final goal is to become familiar with the nested sliding mechanism; For sliding, it is divided into the following articles to complete the interpretation:

  • Sliding base
  • ScrollView sliding source code interpretation
  • NestedScrollView nested slide source code interpretation
  • CoordinatorLayout-AppBarLayout-CollapsingToolbarLayout complex slide logic source code interpretation

In this chapter, this chapter is analyzed from two nested perspectives

  1. A sliding view Angle: involving NestedScrollingChild3 interface and NestedScrollingChildHelper auxiliary class
  2. Slide the parent container perspective: involving NestedScrollingParent3 interface and NestedScrollingParentHelper auxiliary class

This content is divided into three chapters

  1. NestedScrollingChildHelper class
  2. NestedScrollingParentHelper class
  3. Implement processing and call timing

In this case, reading the class is necessary, otherwise you have to remember when it was called, which is not recommended; A portion of the source code will be posted below, in the source code will be some key comments

2, NestedScrollingChildHelper class

Nested subview roles; The main function

  • Whether the event requires notification
  • Event notification

Class contains the following variables:

private ViewParent mNestedScrollingParentTouch; / / touch event relay the parent container private ViewParent mNestedScrollingParentNonTouch; Private final View mView; / / the current container, and also as a nested sliding child role container private Boolean mIsNestedScrollingEnabled; / / the current slide containers support nested private int [] mTempNestedScrollConsumed; // The length of events consumed by x and y; Reduce object generationCopy the code

2.1 Obtaining An Instance

    public NestedScrollingChildHelper(@NonNull View view) {
        mView = view;
    }
Copy the code

2.2 Nested sliding support

This is for the role of the nested subview

public void setNestedScrollingEnabled(boolean enabled) { if (mIsNestedScrollingEnabled) { ViewCompat.stopNestedScroll(mView); / / compatibility mode called} mIsNestedScrollingEnabled = enabled; } public boolean isNestedScrollingEnabled() { return mIsNestedScrollingEnabled; }Copy the code

2.3 Nested sliding correlation methods

To support nested sliding, you must have multiple containers that support nested sliding; As a subview, it needs a set of notifications, so the methods are:

  • Parent container search, judgment
  • Notification of beginning, process, and end

2.3.1 Lookup of nested parent containers

A member variable mNestedScrollingParentTouch, mNestedScrollingParentNonTouch as the parent container cache variable; The direct setting and obtaining methods are as follows

private ViewParent getNestedScrollingParentForType(@NestedScrollType int type) { switch (type) { case TYPE_TOUCH: return mNestedScrollingParentTouch; case TYPE_NON_TOUCH: return mNestedScrollingParentNonTouch; } return null; } private void setNestedScrollingParentForType(@NestedScrollType int type, ViewParent p) { switch (type) { case TYPE_TOUCH: mNestedScrollingParentTouch = p; break; case TYPE_NON_TOUCH: mNestedScrollingParentNonTouch = p; break; }}Copy the code

2.3.2 Support judgment of nested parent containers

public boolean hasNestedScrollingParent() { return hasNestedScrollingParent(TYPE_TOUCH); } public boolean hasNestedScrollingParent(@NestedScrollType int type) { return getNestedScrollingParentForType(type) ! = null; }Copy the code

2.3.3 Slide to start notification

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) { if (hasNestedScrollingParent(type)) { return true; } if (isNestedScrollingEnabled()) {if (isNestedScrollingEnabled()) {ViewParent p = mview.getparent (); View child = mView; while (p ! = null) {// Find not only the direct parent container // compatible call, If the parent container can be used as a nested role if the parent container (ViewParentCompat. OnStartNestedScroll (p, child, mView, axes, type)) { setNestedScrollingParentForType(type, p); / / the cache/here/compatible with the call, the parent container ViewParentCompat. OnNestedScrollAccepted (p, child, mView, axes, type); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } return false; }Copy the code

The search of the parent container, take a delay strategy, in the event, before the query, and in the query, support; So it can be understood as follows:

  1. OnStartNestedScroll: the parent container accepts the event notification method, and the result indicates whether it can be used as the parent container for nested sliding
  2. OnNestedScrollAccepted: Not required, subsequent nested processing indicating that the nested parent container role supports the View is called

2.3.4 Finger swipe notifications

Notice when sliding, divided into sliding before and sliding after; Makes nested sliding handles more flexible before sliding notifications

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) { return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH); } public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) { if (isNestedScrollingEnabled()) { final ViewParent parent = getNestedScrollingParentForType(type); if (parent == null) { return false; } if (dx ! = 0 || dy ! = 0) { int startX = 0; int startY = 0; if (offsetInWindow ! = null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } if (consumed == null) { consumed = getTempNestedScrollConsumed(); } consumed[0] = 0; consumed[1] = 0; ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type); if (offsetInWindow ! = null) { mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return consumed[0] ! = 0 || consumed[1] ! = 0; } else if (offsetInWindow ! = null) { offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }Copy the code

Two two-dimensional arrays are returned as the result; The onNestedPreScroll method of the parent container is used for processing and the sliding processing details are put into two two-dimensional arrays. The commonly used details are consumption length. The return result indicates whether to process before sliding

Post slide notification

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) { return dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, TYPE_TOUCH, null); } public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) { return dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, null); } public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type, @Nullable int[] consumed) { dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed); } private boolean dispatchNestedScrollInternal(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type, @Nullable int[] consumed) { if (isNestedScrollingEnabled()) { final ViewParent parent = getNestedScrollingParentForType(type); if (parent == null) { return false; } if (dxConsumed ! = 0 || dyConsumed ! = 0 || dxUnconsumed ! = 0 || dyUnconsumed ! = 0) { int startX = 0; int startY = 0; if (offsetInWindow ! = null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } if (consumed == null) { consumed = getTempNestedScrollConsumed(); consumed[0] = 0; consumed[1] = 0; } ViewParentCompat.onNestedScroll(parent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed); if (offsetInWindow ! = null) { mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return true; } else if (offsetInWindow ! = null) { offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }Copy the code

Two two-dimensional arrays are returned as the result; The onNestedScroll method of the parent container is used for processing and the sliding processing details are put into two two-dimensional arrays. The commonly used details are consumption length. The return result indicates whether to process before sliding

2.3.5 Glide notification

Gliding also has two opportunities

Before the glide

public boolean dispatchNestedPreFling(float velocityX, float velocityY) { if (isNestedScrollingEnabled()) { ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH); if (parent ! = null) { return ViewParentCompat.onNestedPreFling(parent, mView, velocityX, velocityY); } } return false; }Copy the code

The return result indicates whether the parent container handles the glide; The parent container is processed through onNestedPreFling

After the glide

public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { if (isNestedScrollingEnabled()) { ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH); if (parent ! = null) { return ViewParentCompat.onNestedFling(parent, mView, velocityX, velocityY, consumed); } } return false; }Copy the code

The return result indicates whether the parent container handles the glide; The parent container is processed through onNestedFling

Gliding is a mutually exclusive process while gliding is a relay process

2.3.6 Slide end notification

public void stopNestedScroll() { stopNestedScroll(TYPE_TOUCH); } public void stopNestedScroll(@NestedScrollType int type) { ViewParent parent = getNestedScrollingParentForType(type); if (parent ! = null) {/ / notify nested parent container, sliding over ViewParentCompat. OnStopNestedScroll (parent, mView, type); setNestedScrollingParentForType(type, null); // Clear parent container references}}Copy the code

3, NestedScrollingParentHelper class

As the parent container role of nested slides, it can only be processed when it receives notifications, which is not as complicated as the child view role. In the helper class, only the sliding direction is declared for the period of processing;

Member variables

private int mNestedScrollAxesTouch; / / Touch events, accept the processing, the sliding method private int mNestedScrollAxesNonTouch; // The sliding method of the event when receiving a non-touch eventCopy the code

3.1 Obtaining sliding direction

    public int getNestedScrollAxes() {
        return mNestedScrollAxesTouch | mNestedScrollAxesNonTouch;
    }
Copy the code

3.2 Slide direction setting

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes) { onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH); } public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) { if (type == ViewCompat.TYPE_NON_TOUCH) { mNestedScrollAxesNonTouch = axes; } else { mNestedScrollAxesTouch = axes; }}Copy the code

3.3 Slide direction reset

public void onStopNestedScroll(@NonNull View target) { onStopNestedScroll(target, ViewCompat.TYPE_TOUCH); } public void onStopNestedScroll(@NonNull View target, @NestedScrollType int type) { if (type == ViewCompat.TYPE_NON_TOUCH) { mNestedScrollAxesNonTouch = ViewGroup.SCROLL_AXIS_NONE; } else { mNestedScrollAxesTouch = ViewGroup.SCROLL_AXIS_NONE; }}Copy the code

4. Nested implementation mechanism

As a nested sliding container with a compatible implementation, it must implement the following interface

  • The sliding container interface is ScrollingView
  • Nested sliding parent container interface NestedScrollingParent3
  • Nested sliding subview interface NestedScrollingChild3

Nested interfaces, which can be implemented according to the container role; Method implementation requires the use of helper classes

Read the two auxiliary classes from above; Summarize the functions they have implemented

  1. Whether nesting is supported
  2. Nested notice
  3. Nested slide directions

That is, the implementation method of the child view role basically uses the helper class, while the nested parent container role requires us to increase the implementation logic; Need to achieve functional division:

  1. As a nested subview setting,
  2. Implementation as a nested parent container
  3. Slide relay treatment, and glide treatment

4.1 Support for nested subviews

The constructor for setNestedScrollingEnabled set (true) method

SetNestedScrollingEnabled method

    public void setNestedScrollingEnabled(boolean enabled) {
        mChildHelper.setNestedScrollingEnabled(enabled);
    }
Copy the code

4.2 Support for nested parent containers

public boolean onStartNestedScroll( @NonNull View child, @NonNull View target, int nestedScrollAxes) { return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH); } public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) { return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) ! = 0; }Copy the code

Can slide direction judgment and then decide whether to support; The processing is as follows

    public void onNestedScrollAccepted(
            @NonNull View child, @NonNull View target, int nestedScrollAxes) {
        onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
    }
    
    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
            int type) {
        mParentHelper.onNestedScrollAccepted(child, target, axes, type);
        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type);
    }
Copy the code

It’s still a subview role, so it needs to continue to signal the start of the slide; Nesting is a nested sliding container list. There may be sliding containers in the middle of the list (nesting is not supported). The “parent” of the container after the list group may also be nested sliding. One reason for these situations is that both parent and child views continue to be distributed; The header must be a nested child, the middle child must be a parent, and the tail must be a nested parent

The timing

In the Down event, the startNestedScroll method is called

4.3 Rewrite using auxiliary classes

The following method is overridden directly using helper classes

  • Nested parent exists: hasNestedScrollingParent
  • Does child views support nested sliding setNestedScrollingEnabled, isNestedScrollingEnabled
  • Start notification: startNestedScroll
  • Sliding distribution: dispatchNestedPreScroll, dispatchNestedScroll
  • Glide: dispatchNestedPreFling, dispatchNestedFling
  • Stop scroll: stopNestedScroll

The default type is ViewCompat.TYPE_TOUCH when the sliding type is involved in the parameter

4.4 Sliding relay processing

    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
        onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
    }
    
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
            int type) {
        dispatchNestedPreScroll(dx, dy, consumed, null, type);
    }
Copy the code

As the parent container, it does not handle the event itself, but continues to be distributed as a child view. Nested subviews in the move event process before sliding

public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { onNestedScrollInternal(dyUnconsumed, ViewCompat.TYPE_TOUCH, null); } private void onNestedScrollInternal(int dyUnconsumed, int type, @Nullable int[] consumed) { final int oldScrollY = getScrollY(); scrollBy(0, dyUnconsumed); final int myConsumed = getScrollY() - oldScrollY; if (consumed ! = null) { consumed[1] += myConsumed; } final int myUnconsumed = dyUnconsumed - myConsumed; mChildHelper.dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null, type, consumed); }Copy the code

The parent container first handles the slide, and then passes on the processed condition; When the move event is handled by the nested subview

4.5 Glide is mutually exclusive

    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
        return dispatchNestedPreFling(velocityX, velocityY);
    }
    
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }
Copy the code

Does not process, but continues to distribute as nested subviews; The timing up event is intercepted when nested subviews are processed before

public boolean onNestedFling( @NonNull View target, float velocityX, float velocityY, boolean consumed) { if (! consumed) { dispatchNestedFling(0, velocityY, true); fling((int) velocityY); return true; } return false; }Copy the code

If the notification is not processed when it is received, it is processed. And continues notification processing as a nested subview; The timing up event is intercepted after the nested subview is processed

4.6 Slide End

    public void onStopNestedScroll(@NonNull View target) {
        onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
    }
    public void onStopNestedScroll(@NonNull View target, int type) {
        mParentHelper.onStopNestedScroll(target, type);
        stopNestedScroll(type);
    }
    public void stopNestedScroll(int type) {
        mChildHelper.stopNestedScroll(type);
    }
Copy the code

Since it is still a nested subview role, it also needs to notify the nested parent container it works with that it is finished; When the event is up or cancel

4.7 Nested subviews are processed preferentially

Android, from the container’s default interception mechanism, the parent container intercepts first; But nesting makes an extra judgment,

Sliding event interception is judged this way

yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0)
Copy the code

The sliding axis is 0, that is, neither x nor y; This means that when it acts as a nested parent, no nested children are passed to it;

In addition, if the slide has already been intercepted, you do not want others to be intercepted again. Since nested interceptors already provide interactive methods, failure to do so will result in conflicts with the default event mechanism; So, if this is the case, rewrite the parent container to support nested sliding

5 subtotal

In general, nested sliding abstracts interfaces and helper classes to help developers implement it. Which realizes the core thought trigger point

  1. Nested organizational relationships
  2. Nested mutual notification processing
  3. Do I need to deal with it and how do I deal with it

If you gain something from this article, please give the author an encouragement and a thumbs-up. Thank you for your support

Technology changes quickly, but the basic technology, the theoretical knowledge is always the same; The author hopes to share the basic knowledge of common technical points in the rest of his life. If you think the article is well written, please pay attention and like it. If there are mistakes in the article, please give me more advice!

ScrollView sliding source code interpretation

CoordinatorLayout-AppBarLayout-CollapsingToolbarLayout complex slide logic source code interpretation