What is nested scrolling

A mechanism for collaboration when both the parent container and child controls support scrolling.

Application scenarios of nested scrolling

1. Address the limitations of event distribution mechanism: once the parent container intercepts the event and handles it by itself, the subsequent events cannot be passed to the child control for consumption.

2. A solution to event conflict

2.1 CoordinatorLayout cooperate RecyclerView | NestedScrollView

Scroll the parent container first and then the child controls:

2.2 swiperefreshlayout

Dealing with gesture conflicts:

2.3 bottomSheetDialog

Dealing with gesture conflicts:

Three nested scrolling principle analysis

Nested scrolling has been added to android5.0, and previously compatible solutions were supported by four classes:

Interface class: NestedScrollingChild NestedScrollingParent implementation class: NestedScrollingChildHelper NestedScrollingParentHelper

sequenceDiagram NestedScrollingChild->>NestedScrollingParent: StartNestedScroll () NestedScrollingParent->>NestedScrollingChild: OnStartNestedScroll () loop 1 - n scroll event Note over NestedScrollingParent NestedScrollingChild: 1. NestedScrollingParent ->>NestedScrollingParent: dispatchNestedPreScroll() NestedScrollingParent->>NestedScrollingChild: onNestedPreScroll() Note over NestedScrollingParent,NestedScrollingChild: 2. NestedScrollingChild->>NestedScrollingParent: dispatchNestedScroll() NestedScrollingParent->>NestedScrollingChild: onNestedScroll() Note over NestedScrollingParent,NestedScrollingChild: 3. End Rect RGB (0, 255, 255) opt 0-1 inertial scroll NestedScrollingChild-->>NestedScrollingParent: dispatchNestedPreFling() NestedScrollingParent-->>NestedScrollingChild: onNestedPreFling() NestedScrollingChild-->>NestedScrollingParent: dispatchNestedFling() NestedScrollingParent-->>NestedScrollingChild: onNestedFling() end end NestedScrollingChild-->>NestedScrollingParent: NestedScrollingParent->>NestedScrollingChild: onStopNestedScroll()

NestedScrollingChild class diagram

classDiagram
NestedScrollingChild <|-- NestedScrollingChild2
NestedScrollingChild2 <|-- NestedScrollingChild3
NestedScrollingChild2 <|.. RecyclerView
NestedScrollingChild3 <|.. RecyclerView
NestedScrollingChild3 <|.. NestedScrollView
<<interface>> NestedScrollingChild
class NestedScrollingChild{
+setNestedScrollingEnabled(boolean)
+isNestedScrollingEnabled()
+startNestedScroll(int)
+stopNestedScroll()
+hasNestedScrollingParent()
+dispatchNestedScroll(int, int,int, int, int[])
+dispatchNestedPreScroll(int, int, int[], int[])
+dispatchNestedFling(float, float, boolean)
+dispatchNestedPreFling(float, float)
}
<<interface>> NestedScrollingChild2
class NestedScrollingChild2{
+startNestedScroll(int,int)
+stopNestedScroll(int)
+hasNestedScrollingParent(int)
+dispatchNestedScroll(int, int,int, int, int[],int)
+dispatchNestedPreScroll(int, int, int[], int[],int)
}
<<interface>> NestedScrollingChild3
class NestedScrollingChild3{
+dispatchNestedScroll(int, int,int, int, int[],int,int[])
}
class RecyclerView
class NestedScrollView

NestedScrollingParent class diagram

classDiagram
NestedScrollingParent <|-- NestedScrollingParent2
NestedScrollingParent2 <|-- NestedScrollingParent3
NestedScrollingParent2 <|.. CoordinatorLayout
NestedScrollingParent3 <|.. CoordinatorLayout
NestedScrollingParent3 <|.. NestedScrollView
NestedScrollingParent3 <|.. MotionLayout
<<interface>> NestedScrollingParent
class NestedScrollingParent{
+onStartNestedScroll(View,View, @ScrollAxis int)
+onNestedScrollAccepted(View,View, @ScrollAxis int)
+onStopNestedScroll(View)
+onNestedScroll(View, int, int, int, int)
+onNestedPreScroll(View, int, int,int[])
+onNestedFling(View, float, float, boolean)
+onNestedPreFling(View, float, float)
+getNestedScrollAxes()
}
<<interface>> NestedScrollingParent2
class NestedScrollingParent2{
+onStartNestedScroll(View,View, @ScrollAxis int, @NestedScrollType int)
+onNestedScrollAccepted(View,View, @ScrollAxis int, @NestedScrollType int)
+onStopNestedScroll(View, @NestedScrollType int)
+onNestedScroll(View, int, int, int, int, @NestedScrollType int)
+onNestedPreScroll(View, int, int,int[], @NestedScrollType int)
}
<<interface>> NestedScrollingParent3
class NestedScrollingParent3{
+onNestedScroll(View, int, int, int, int, @NestedScrollType int,int[])
}
class CoordinatorLayout
class NestedScrollView

NestedScrollingChildHelper class diagram

classDiagram
class NestedScrollingChildHelper{
+NestedScrollingChildHelper(View)
+setNestedScrollingEnabled(boolean)
+isNestedScrollingEnabled()
+startNestedScroll(int)
+stopNestedScroll()
+hasNestedScrollingParent()
+dispatchNestedScroll(int, int,int, int, int[])
+dispatchNestedPreScroll(int, int, int[], int[])
+dispatchNestedFling(float, float, boolean)
+dispatchNestedPreFling(float, float)
+onDetachedFromWindow()
+onStopNestedScroll(View)
}

NestedScrollingParentHelper class diagram

classDiagram
class NestedScrollingParentHelper{
+NestedScrollingParentHelper(ViewGroup)
+onNestedScrollAccepted(View,View, @ScrollAxis int)
+onStopNestedScroll(View)
+getNestedScrollAxes()
}

An implementation of nested scrolling was written after 5.0ViewandViewGroupIn:

StartNestedScroll () ViewGroup->>View: OnStartNestedScroll () loop 1-n scroll events Note over ViewGroup,View: 1. View->>ViewGroup: dispatchNestedPreScroll() ViewGroup->>View: onNestedPreScroll() Note over ViewGroup,View: 2. View->>ViewGroup: dispatchNestedScroll() ViewGroup->>View: onNestedScroll() Note over ViewGroup,View: 3. End Rect RGB (0, 255, 255) opt 0-1 inertia scroll View-->>ViewGroup: dispatchNestedPreFling() ViewGroup-->>View: onNestedPreFling() View-->>ViewGroup: dispatchNestedFling() ViewGroup-->>View: OnNestedFling () end end View-->>ViewGroup: stopNestedScroll() ViewGroup->>View: onStopNestedScroll()

Nested scrollrelated methods in View and ViewGroup:

classDiagram
View <|-- ViewGroup
class View{
+setNestedScrollingEnabled(boolean)
+isNestedScrollingEnabled()
+startNestedScroll(int)
+stopNestedScroll()
+hasNestedScrollingParent()
+dispatchNestedScroll(int, int,int, int, int[])
+dispatchNestedPreScroll(int, int, int[], int[])
+dispatchNestedFling(float, float, boolean)
+dispatchNestedPreFling(float, float)
}
class ViewGroup{
+onStartNestedScroll(View,View, @ScrollAxis int)
+onNestedScrollAccepted(View,View, @ScrollAxis int)
+onStopNestedScroll(View)
+onNestedScroll(View, int, int, int, int)
+onNestedPreScroll(View, int, int,int[])
+onNestedFling(View, float, float, boolean)
+onNestedPreFling(View, float, float)
+getNestedScrollAxes()
}

Nested scrolling triggers and stops

Start nested scrolling:

NestedScrollingChild.startNestedScroll(int axes)
NestedScrollingChild.stopNestedScroll()
Copy the code

Stop nested scrolling:

NestedScrollingChild.setNestedScrollingEnabled(false)
NestedScrollingChild.stopNestedScroll()
Copy the code

The relationship between nested scrolling and event distribution mechanisms (using RecyclerView as an example)

public boolean onTouchEvent(MotionEvent e) { ... switch (action) { case MotionEvent.ACTION_DOWN: { ... //1. StartNestedScroll (nestedscrolsop, TYPE_TOUCH); } break; case MotionEvent.ACTION_MOVE: { ... If (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {// Distance consumed by the child control = total distance moved - distance consumed by the parent control dx-= mScrollConsumed[0]; dy -= mScrollConsumed[1]; vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); // Updated the nested offsets mNestedOffsets[0] += mScrollOffset[0]; . If (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedX, unconsumedX); unconsumedY, mScrollOffset)) { ... } }break; case MotionEvent.ACTION_UP: { ... //4. The internal fling() method gives priority to the parent container to perform the inertial slip if (! dispatchNestedPreFling(velocityX, velocityY)) { ... //5. The parent control does not consume inertial scrolling, and the child control handles it by itself. Fling(velocityX, velocityY, canScroll); . mViewFlinger.fling(velocityX, velocityY); // End nested scroll internally by resetTouch() stopNestedScroll(TYPE_TOUCH); } }break; }Copy the code

Nested fling version differences

Fling events do not support partial consumption. The parent container can either intercept the entire Fling event or hand it over to the child.

Support library after V26: Added support for some fling events to the parent container

The core implementation is the NestedScrollingChild2 and NestedScrollingParent2 classes, overriding the nested scrollingrelated methods and adding the type parameter. Where there was only one manually triggered NestedScroll, there are now two nested scrolls: If manually fired -TYPE_TOUCH=0 and code fired -TYPE_NON_TOUCH=1, nestedscroll is converted to TYPE_NON_TOUCH, which indirectly supports the partial consumption of nestedfling.

Take NestedScrollView as an example:

The fling method starts the nested scrolling triggered by the code:

public void fling(int velocityY) { if (this.getChildCount() > 0) { this.mScroller.fling(this.getScrollX(), this.getScrollY(), 0, velocityY, 0, 0, -2147483648, 2147483647, 0, 0); this.runAnimatedScroll(true); } } private void runAnimatedScroll(boolean participateInNestedScrolling) { if (participateInNestedScrolling) { TYPE_NON_TOUCH this.startnestedScroll (2, 1); } else { this.stopNestedScroll(1); } this.mLastScrollerY = this.getScrollY(); ViewCompat.postInvalidateOnAnimation(this); }Copy the code

Call the dispatchNestedPreScroll() and dispatchNestedScroll() methods in the computeScroll method:

public void computeScroll() { if (! this.mScroller.isFinished()) { ... / / 1. For the parent container preferred consumer enclosing dispatchNestedPreScroll (0, unconsumed, enclosing mScrollConsumed, (int []) null, 1); // Child scrollable distance = total distance - Consumed by the parent container -= this.mscrollconsumed [1]; . if (unconsumed ! = 0) { mode = this.getScrollY(); OverScrollByCompat (0, unconsumed, this.getScrollX(), mode, 0, range, 0, 0, false); . / / 3. The distance will have distance and not consumption is passed to the parent container handling this. DispatchNestedScroll (scrolledByMe 0, 0, unconsumed, enclosing mScrollOffset, 1, this.mScrollConsumed); . }... if (! this.mScroller.isFinished()) { ViewCompat.postInvalidateOnAnimation(this); } else {//4. Stop the nested scroll triggered by the code when the rolling scroll ends this.stopnestedScroll (1); }}}Copy the code