At present, the classes that have implemented the modified interface include coordinatorlayout, nestedscrollview and swiperefreshlayout. It is usually nested and sliding with NestedScrollingChild.
- boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
This method is called when Scrolling Child starts sliding
When Scrolling Child calls onStartNestedScroll method, the onStartNestedScroll method of Scrolling parent will be called back through NestedScrollingChildHelper. If true is returned, the onNestedScrollAccepted(View child, View target, int nestedScrollAxes) method of Scrolling parent will be called back.
Target represents the View that initiated the sliding event, Child is the direct Child View of ViewParent, including target, and nestedScrollAxes represents the sliding direction.
- void onNestedScrollAccepted(View child, View target, int nestedScrollAxes)
If the onStartNestedScroll of the scrolling parent returns true, the onNestedScrollAccepted(View child, View target, int nestedScrollAxes) method of the scrolling parent will be called back.
- boolean onNestedPreScroll(View target, int dx, int dy, int[] consumed)
Before Scrolling Child slides, Scrolling Parent can perform corresponding processing before Scrolling Child
If Scrolling Child calls dispatchNestedPreFling(float velocityX, float velocityY), NestedScrollingChildHelper will call back the onenestedprescroll method of Scrolling parent
We will not introduce the following methods one by one. It almost corresponds to the Scrolling Child method.
[](
)NetsedScrollingChildHelper related methods
RecyclerView implements the NestedScrollingChild interface, so we take RecyclerView as an example to explore the specific application of netsedscrollingchild helper in detail
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { ... mScrollingChildHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); } ... @Override public void setNestedScrollingEnabled(boolean enabled) { mScrollingChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mScrollingChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mScrollingChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mScrollingChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mScrollingChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); } ...
It can be seen from the code that RecyclerView acts as an agent, and many of its logic is actually handed over to NestedScrollingChildHelper to help it complete. Let's take a look at the methods in NestedScrollingChildHelper
/** * Start a new nested scroll for this view. * * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass * method/{@link NestedScrollingChild} interface method with the same signature to implement * the standard policy.</p> * * @param axes Supported nested scroll axes. * See {@link NestedScrollingChild#startNestedScroll(int)}. * @return true if a cooperating parent view was found and nested scrolling started successfully */ public boolean startNestedScroll(int axes) { if (hasNestedScrollingParent()) { // Already in progress return true; } if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; while (p != null) { if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { mNestedScrollingParent = p; ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } return false; }
-
Step 1: judge whether P is empty or not. Start from P (the initial value is the direct parent View of RecyclerView) to judge whether it supports nested sliding. If yes, return true;
-
Step 2: if P does not support nested sliding, point P to p.getParent(), and cycle the first step;
-
Step 3: if all P's are cycled and no View supporting nested sliding is found, false is returned.
/** * Dispatch one step of a nested scrolling operation to the current nested scrolling parent. * * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass * method/{@link NestedScrollingChild} interface method with the same signature to implement * the standard policy.</p> * * @return true if the parent consumed any of the nested scroll */ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { 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]; } ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return true; } else if (offsetInWindow != null) { // No motion, no dispatch. Keep offsetInWindow up to date. offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }
When the childView slides, first obtain the position of the childView on the screen and record the X and Y coordinates. Because the initialization of mNestedScrollingParent was completed in the startNestedScroll method in the previous step, ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed,dyUnconsumed), finally re obtain the position of the sliding childView on the screen, and re assign the X and Y axis coordinates in the upper left corner of the childView as the difference between the current position and the initial position; When the childView does not slide, directly assign the X and Y axis coordinates in the upper left corner of the childView to 0.
After reading the above two main methods, we can draw a conclusion: when we call the onStartNested method of Scrolling Child, we will use ChildHelper to find out whether there is a corresponding Scrolling Parent. If so, we will call back the corresponding method. The same is true for dispatchNestedPreScroll, dispatchNestedScroll and dispatchNestedPreFling.
public boolean onTouchEvent(MotionEvent e) { ... // If an Item handles a Touch event, it directly returns true if (dispatchOnItemTouch(e)) { cancelTouch(); return true; } if (mLayout == null) { return false; } ... switch (action) { case MotionEvent.ACTION_DOWN: { mScrollPointerId = e.getPointerId(0); mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; if (canScrollHorizontally) { nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; } if (canScrollVertically) { nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; } // In action_ Call startNestedScroll when down startNestedScroll(nestedScrollAxis); } break; case MotionEvent.ACTION_MOVE: { ... // In action_ When moving, call back the dispatchNestedPreScroll method if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { // Subtract the value of the consumption of the Scrolling Parent dx -= mScrollConsumed[0]; dy -= mScrollConsumed[1]; vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); // Updated the nested offsets mNestedOffsets[0] += mScrollOffset[0]; mNestedOffsets[1] += mScrollOffset[1]; } ... if (mScrollState == SCROLL_STATE_DRAGGING) { mLastTouchX = x - mScrollOffset[0]; mLastTouchY = y - mScrollOffset[1]; // The onNestedScroll method will be called back in the scrollByInternal method if (scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, vtev)) { getParent().requestDisallowInterceptTouchEvent(true); } if (mGapWorker != null && (dx != 0 || dy != 0)) { mGapWorker.postFromTraversal(this, dx, dy); } }