NestScrollView principle analysis, done Android for three years

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;



    } 

  1. 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;

  2. Step 2: if P does not support nested sliding, point P to p.getParent(), and cycle the first step;

  3. 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);



                    }



                }

 

Keywords: Android Design Pattern

Added by mattastic on Fri, 03 Sep 2021 21:36:36 +0300