關(guān)于CoordinatorLayout AppBarLayout原理的一些分析

這幾天學(xué)了一些CoordinatorLayout瓦呼、AppBarLayout配合使用的一些方法岖寞,之前還寫了一篇CoordinatorLayout Behavior一些筆記,通過(guò)這幾天對(duì)源碼的閱讀,現(xiàn)在對(duì)CoordinatorLayout宠漩、AppBarLayout這部分的內(nèi)容有了更深一層的理解举反,接下來(lái)我就把我所理解的源碼簡(jiǎn)單的分析一下。

一扒吁、 NestedScrolling機(jī)制

CoordinatorLayout火鼻、AppBarLayout分別實(shí)現(xiàn)了NestedScrolling機(jī)制中需要的接口和接口中的一些方法,如果大家對(duì)NestedScrolling不是很了解,可以先去網(wǎng)上了解一下魁索,這里我簡(jiǎn)單說(shuō)明一下這個(gè)機(jī)制的原理:Nested這個(gè)單詞的意思是“嵌套”融撞,這個(gè)機(jī)制其實(shí)就是嵌套滑動(dòng)的一種處理機(jī)制,它和之前只能單一View消耗滑動(dòng)事件的處理機(jī)制不同粗蔚,它會(huì)在子View處理滑動(dòng)事件時(shí)尝偎,先將滑動(dòng)事件傳遞到父View中,詢問(wèn)父View是否需要消耗滑動(dòng)事件鹏控,如果父View需要消耗滑動(dòng)事件冬念,子View會(huì)將此次x,y滑動(dòng)的距離先傳遞到父View中牧挣,父View會(huì)先消耗滑動(dòng)事件急前,如果父View沒(méi)消耗全部的滑動(dòng)距離,子View會(huì)消耗剩余的滑動(dòng)距離瀑构,如果剩余的滑動(dòng)距離大于子View剩余需要的滑動(dòng)距離(例如RecyclerView距離自身Content滑動(dòng)到頂部的距離只有10,但是此次滑動(dòng)距離dy有50裆针,父View消耗了30,剩余20大于RecyclerView剩余需要滑動(dòng)的距離)寺晌,子View會(huì)把剩下的滑動(dòng)距離再次傳遞給父View世吨,由父View去消耗。
我推薦兩篇我覺(jué)得還挺不錯(cuò)的文章可以幫助理解這個(gè)機(jī)制:Android NestedScrolling機(jī)制完全解析 帶你玩轉(zhuǎn)嵌套滑動(dòng)android NestedScroll嵌套滑動(dòng)機(jī)制完全解析-原來(lái)如此簡(jiǎn)單

二呻征、可以實(shí)現(xiàn)的效果

說(shuō)了這么多耘婚,這個(gè)機(jī)制到底可以實(shí)現(xiàn)什么樣的效果呢,其實(shí)就是滑動(dòng)起來(lái)非常的順滑陆赋,例如沐祷,我在界面中放了一個(gè)RecyclerView,RecyclerView上面放了一個(gè)AppBarLayout包裹的ImageView攒岛,當(dāng)我滑動(dòng)這個(gè)界面時(shí)赖临,不會(huì)像原來(lái)那種機(jī)制需要在RecyclerView滑動(dòng)到頂部時(shí),需要抬起手指進(jìn)行下次滑動(dòng)才能把RecyclerView上面的View滑出屏幕以外灾锯,效果圖如下:


效果圖

三兢榨、原理分析

下面開始進(jìn)行我對(duì)源碼閱讀的分析理解,這里主要分成兩個(gè)部分顺饮,主要是RecyclerView吵聪、CoordinatorLayout 、AppBarLayout如何實(shí)現(xiàn)了NestedScrolling機(jī)制兼雄。
先簡(jiǎn)要概括一下總體的中心思想吟逝,根據(jù)上文對(duì)NestedScrolling的介紹,這里的RecyclerView就是子View君旦,CoordinatorLayout就是父View澎办,AppBarLayout是父View在判斷是否消耗事件嘲碱,在判斷方法中主要依據(jù)的View金砍。主要的過(guò)程都是在RecyclerView的onTouchEvent中局蚀,分別在Down和Move事件中完成了整個(gè)機(jī)制的流程。這里說(shuō)一句題外話恕稠,為什么ListView琅绅、GirdView不能實(shí)現(xiàn)這種效果?因?yàn)檫@兩個(gè)View并沒(méi)有實(shí)現(xiàn)NestedScrolling機(jī)制中相關(guān)的方法鹅巍,可以看一下RecyclerView源碼千扶,我們會(huì)發(fā)現(xiàn)RecycerView定義如下:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild

1.RecyclerView中MotionEvent.ACTION_DOWN做了哪些事兒?

這里我先來(lái)一張流程圖:


ActionDown.png

這里所做的一件事兒骆捧,就是子View在滑動(dòng)事件開始時(shí)澎羞,傳遞給父View,父View會(huì)去判斷是否需要消耗此次事件敛苇,下面就是源碼的分析

//RecyclerView
@Override
    public boolean onTouchEvent(MotionEvent e){
        //.................
        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;
                }
                //調(diào)用NestedScrollingChildHelper
                startNestedScroll(nestedScrollAxis);
            } break;
            //..........
        }

        if (!eventAddedToVelocityTracker) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();

        return true;
    }

上面的startNestedScroll方法就會(huì)調(diào)用到NestedScrollingChildHelper中的startNestedScroll方法妆绞。Helper中該方法的實(shí)現(xiàn)如下:

    //NestedScrollingChildHelper
    public boolean startNestedScroll(int axes) {
        if (hasNestedScrollingParent()) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            //通過(guò)while循環(huán),不斷的去判斷是否有View的ParentView需要消耗這次滑動(dòng)事件
            while (p != null) {
                //判斷parent是否需要消耗
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                    mNestedScrollingParent = p;
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                    //父View消耗滑動(dòng)事件
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
       //循環(huán)結(jié)束枫攀,沒(méi)有發(fā)現(xiàn)需要消耗的View
        return false;
    }

在這個(gè)方法中括饶,所做的事兒只有一件,去循環(huán)遍歷并且詢問(wèn)這個(gè)View的ParentView和ParentView的ParentView是否需要消耗這次事件来涨,如果有消耗的返回true否則返回false图焰,這里判斷的方法使用了ViewParentCompat.onStartNestedScroll(p, child, mView, axes),這個(gè)方法實(shí)現(xiàn)很簡(jiǎn)單蹦掐,里面僅僅是調(diào)用了我們傳入的參數(shù)p的onStartNestedScroll方法技羔,在我的事例中,p就是CoordinatorLayout卧抗,所以我們可以直接查看CoordinatorLayout中onStartNestedScroll方法的實(shí)現(xiàn)

//CoordinatorLayout
@Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        boolean handled = false;

        final int childCount = getChildCount();
        //仍然是遍歷子View堕阔,判斷是否有View需要消耗
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            //判斷behavior是否為空
            if (viewBehavior != null) {
                //獲取View是否消耗滑動(dòng)事件
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

                lp.acceptNestedScroll(accepted);
            } else {
                lp.acceptNestedScroll(false);
            }
        }
        return handled;
    }

這里我們可以看到,當(dāng)父View也就是CoordinatorLayout判斷是否消耗滑動(dòng)事件的方式也很簡(jiǎn)單颗味,就是遍歷自己的子View超陆,如果子View有消耗就返回true,這里使用的是 “|=” 只要有子View需要接收便是true浦马,接著在當(dāng)前例子中时呀,ImageView包裹在AppBarLayout,那么在這個(gè)函數(shù)遍歷中晶默,就會(huì)獲取到AppBarLayout的Behavior谨娜,并且調(diào)用AppBarLayout的中Behavior的onStartNestedScroll方法,就是上面時(shí)序圖的最后一個(gè)LifeLine磺陡,AppBarLayout的中Behavior的onStartNestedScroll實(shí)現(xiàn)如下:

 //AppBarLayout$Behavior
 @Override
        public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                View directTargetChild, View target, int nestedScrollAxes) {
            // Return true if we're nested scrolling vertically, and we have scrollable children
            // and the scrolling view is big enough to scroll
            final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
                    && child.hasScrollableChildren()
                    && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();

            if (started && mOffsetAnimator != null) {
                // Cancel any offset animation
                mOffsetAnimator.cancel();
            }

            // A new nested scroll has started so clear out the previous ref
            mLastNestedScrollingChildRef = null;

            return started;
        }

這里可以看到趴梢,在AppBarLayout$Behavior這個(gè)類的onStartNestedScroll漠畜,會(huì)根據(jù)當(dāng)前的nestedScrollAxes和自身的一些條件判斷是否需要消耗這次滑動(dòng)事件,這里也插一句題外話坞靶,我之前疑惑了半天憔狞,我發(fā)現(xiàn)CoordinatorLayout并沒(méi)有給AppBarLayout在哪里設(shè)置了AppBarLayout$Behavior,我在CoordinatorLayout代碼中并沒(méi)有找到彰阴,后來(lái)忽然發(fā)現(xiàn)瘾敢,原來(lái)是用了注解的形式在AppBarLayout開頭聲明了

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout

這樣到此為止,我們第一個(gè)階段的分析就完成了尿这,當(dāng)RecyclerView發(fā)生了MotionEvent.ACTION_DOWN事件時(shí)簇抵,經(jīng)歷了NestedScrollingChildHelper->ViewParentCompat->CoordinatorLayout->AppBarLayout來(lái)完成NestedScrolling機(jī)制中的第一步,子View在滑動(dòng)事件發(fā)生時(shí)射众,告知父View是否需要消耗事件

2.RecyclerView中MotionEvent.ACTION_MOVE做了哪些事兒碟摆?

同樣這里也首先來(lái)一張流程圖:

ActionMove.png

這里做的事兒,就是在父View需要處理滑動(dòng)事件時(shí)叨橱,先將滑動(dòng)事件傳遞到父View典蜕,然后拿到剩下未消耗的距離自己消耗,如果在自己消耗后還有剩余雏逾,那么在傳遞給父View嘉裤,下面開始一步一步的分析源碼

  //RecyclerView
 @Override
    public boolean onTouchEvent(MotionEvent e) {
         //...............
        switch (action) {
            //..................
            case MotionEvent.ACTION_MOVE: {
                final int index = e.findPointerIndex(mScrollPointerId);
                if (index < 0) {
                    Log.e(TAG, "Error processing scroll; pointer index for id " +
                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                    return false;
                }

                final int x = (int) (e.getX(index) + 0.5f);
                final int y = (int) (e.getY(index) + 0.5f);
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;
                //調(diào)用dispatchNestedPreScroll方法并且mScrollConsumed數(shù)組記錄消耗
                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
                    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];
                    //scrollByInternal傳遞剩余消耗
                    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;
            //.........................
        }

        if (!eventAddedToVelocityTracker) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();

        return true;
    }

這里首先是在子View消耗事件之前,通過(guò)調(diào)用dispatchNestedPreScroll方法栖博,如果父View消耗事件屑宠,則子View的dx,dy會(huì)減去已經(jīng)消耗掉的仇让,dispatchNestedPreScroll主要調(diào)用了NestedScrollingChildHelper的dispatchNestedPreScroll方法典奉,我們看一下實(shí)現(xiàn)

//NestedScrollingChildHelper
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
            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) {
                    if (mTempNestedScrollConsumed == null) {
                        mTempNestedScrollConsumed = new int[2];
                    }
                    consumed = mTempNestedScrollConsumed;
                }
                consumed[0] = 0;
                consumed[1] = 0;
                //這里調(diào)用了onNestedPreScroll方法詢問(wèn)父View是否消耗
                ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);

                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;
    }

這里依然使用了ViewParentCompat的方法,ViewParentCompat.onNestedPreScroll方法依然是調(diào)用我們傳入的父View的onNestedPreScroll方法丧叽,這里我的父View依然還是CoordinatorLayout卫玖,這里沒(méi)用循環(huán)遍歷,是因?yàn)橹拔覀円呀?jīng)在ViewParentCompat.startNestedScroll遍歷中保存了mNestedScrollingParent為CoordinatorLayout踊淳,所以我們下一步可以直接查看CoordinatorLayout的onNestedPreScroll

//CoordinatorLayout
 @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        int xConsumed = 0;
        int yConsumed = 0;
        boolean accepted = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                //傳遞給子View進(jìn)行消耗
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);

                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                        : Math.min(xConsumed, mTempIntPair[0]);
                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                        : Math.min(yConsumed, mTempIntPair[1]);

                accepted = true;
            }
        }

        consumed[0] = xConsumed;
        consumed[1] = yConsumed;

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

可以看到假瞬,CoordinatorLayout對(duì)于是否消耗事件,依然是傳遞給子View去消耗迂尝,在我們例子中的這個(gè)布局下能夠消耗掉這個(gè)事件的View就是AppBarLayout脱茉,這樣事件就又傳遞給了CoordinatorLayout的子View去消耗,消耗完了以后垄开,可以看到下面還調(diào)用了onChildViewsChanged這方法琴许,這個(gè)方法的作用是做一些和Behavior相關(guān)的操作,有關(guān)這部分內(nèi)容可以看我的上篇文章CoordinatorLayout Behavior一些筆記溉躲,這樣對(duì)于viewBehavior.onNestedPreScroll這里榜田,我們需要查看AppBarLayout$Behavior中的實(shí)現(xiàn):

//AppBarLayout$Behavior
@Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                View target, int dx, int dy, int[] consumed) {
            if (dy != 0 && !mSkipNestedPreScroll) {
                int min, max;
                if (dy < 0) {
                    // We're scrolling down
                    min = -child.getTotalScrollRange();
                    max = min + child.getDownNestedPreScrollRange();
                } else {
                    // We're scrolling up
                    min = -child.getUpNestedPreScrollRange();
                    max = 0;
                }
                //AppbarLayout只消耗dy的事件益兄,將消耗的事件賦值給consumed[1]并且scroll自身內(nèi)容
                consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
            }
        }

到這里,如果AppBarLayout需要消耗滑動(dòng)事件的話箭券,就會(huì)消耗并且滾動(dòng)自己的內(nèi)容净捅。大家有沒(méi)有好奇一點(diǎn),整個(gè)過(guò)程并沒(méi)有返回值邦鲫,那么RecyclerView是如何通過(guò)一大堆調(diào)用拿到AppBarLayout的消耗呢灸叼?其實(shí)很簡(jiǎn)單神汹,就是Java中傳遞數(shù)組時(shí)庆捺,和C++中按值傳遞不一樣,Java中非基本類型的傳遞類似于C++中按引用傳遞屁魏,還記得我們RecyclerView中調(diào)用dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)這個(gè)方法么滔以?這里mScrollConsumed數(shù)組就是傳遞到onNestedPreScroll中的 int[] consumed,所以只要給 int[] consumed賦值氓拼,就可以在RecyclerView拿到消耗的dx你画,dy,分別對(duì)應(yīng)mScrollConsumed[0]和mScrollConsumed[1]桃漾,接著在AppBarLayout處理完以后坏匪,我們還是看上面的時(shí)序圖,會(huì)發(fā)現(xiàn)撬统,NestedScrollingChildHelper中是需要有返回值的适滓,需要RecyclerView判斷父View是否消耗了滑動(dòng)事件,我們可以看上面NestedScrollingChildHelper的dispatchNestedPreScroll方法中在子View處理消耗事件后恋追,會(huì)return consumed[0] != 0 || consumed[1] != 0;

// NestedScrollingChildHelper
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
                //........................
                return consumed[0] != 0 || consumed[1] != 0;
            } else if (offsetInWindow != null) {
                offsetInWindow[0] = 0;
                offsetInWindow[1] = 0;
            }
        }
        return false;
    }

這樣這個(gè)父View的消耗就在子View滑動(dòng)之前完成了凭迹,接著就是子View的滑動(dòng),并且如果還有沒(méi)消耗完的滑動(dòng)距離會(huì)傳遞給父View讓父View去處理苦囱,這里的過(guò)程主要就是在RecyclerView的scrollByInternal方法中了:

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

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            eatRequestLayout();
            onEnterLayoutOrScroll();
            TraceCompat.beginSection(TRACE_SCROLL_TAG);
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
            TraceCompat.endSection();
            repositionShadowingViews();
            onExitLayoutOrScroll();
            resumeRequestLayout(false);
        }
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }

         //dispatchNestedScroll方法傳遞consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset給父View
        if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
            // 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];
        } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
            if (ev != null) {
                pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
            }
            considerReleasingGlowsOnScroll(x, y);
        }
        if (consumedX != 0 || consumedY != 0) {
            dispatchOnScrolled(consumedX, consumedY);
        }
        if (!awakenScrollBars()) {
            invalidate();
        }
        return consumedX != 0 || consumedY != 0;
    }

scrollByInternal在onTouchEvent中會(huì)被調(diào)用嗅绸,scrollByInternal通過(guò)調(diào)用dispatchNestedScroll把事件傳遞給父View,其實(shí)仍然是調(diào)用了NestedScrollingChildHelper的dispatchNestedScroll撕彤,該方法的實(shí)現(xiàn):

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;
    }

ViewParentCompat.onNestedScroll方法依然是調(diào)用了CoordinatorLayout的onNestedScroll方法鱼鸠,實(shí)現(xiàn)如下:

//CoordinatorLayout
@Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed) {
        final int childCount = getChildCount();
        boolean accepted = false;

        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
                        dxUnconsumed, dyUnconsumed);
                accepted = true;
            }
        }

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

這也依然一樣,CoordinatorLayout會(huì)循環(huán)遍歷羹铅,交給子View去處理蚀狰,這里仍然還是AppBarLayout$Behavior的onNestedScroll:

//AppBarLayout$Behavior
 @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed) {
            if (dyUnconsumed < 0) {
                // If the scrolling view is scrolling down but not consuming, it's probably be at
                // the top of it's content
                scroll(coordinatorLayout, child, dyUnconsumed,
                        -child.getDownNestedScrollRange(), 0);
                // Set the expanding flag so that onNestedPreScroll doesn't handle any events
                mSkipNestedPreScroll = true;
            } else {
                // As we're no longer handling nested scrolls, reset the skip flag
                mSkipNestedPreScroll = false;
            }
        }

到這未消耗的事件就又傳遞到AppBarLayout了,這里的注釋很清晰:If the scrolling view is scrolling down but not consuming, it's probably be at the top of it's content睦裳,翻譯一下造锅,就是如果scrolling view 向下滾動(dòng),但是沒(méi)有消耗滾動(dòng)事件廉邑,可能是已經(jīng)滑倒了頂部哥蔚,例如RecyclerView已經(jīng)滑倒了第一個(gè)Item倒谷,然后AppBarLayout就會(huì)消耗剩余的事件在scroll方法中
到此,整個(gè)流程就已經(jīng)清晰明了了糙箍,整個(gè)Scrolling機(jī)制在CoordinatorLayout AppBarLayout中就是這么實(shí)現(xiàn)的渤愁,整體的流程的概覽就可以參照上面的兩個(gè)時(shí)序圖,對(duì)整個(gè)源碼的分析和理解后深夯,以后在使用起來(lái)我們就會(huì)更加的得心應(yīng)手抖格。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咕晋,隨后出現(xiàn)的幾起案子雹拄,更是在濱河造成了極大的恐慌,老刑警劉巖掌呜,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滓玖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡质蕉,警方通過(guò)查閱死者的電腦和手機(jī)势篡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)模暗,“玉大人禁悠,你說(shuō)我怎么就攤上這事《矣睿” “怎么了碍侦?”我有些...
    開封第一講書人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)顾孽。 經(jīng)常有香客問(wèn)我祝钢,道長(zhǎng),這世上最難降的妖魔是什么若厚? 我笑而不...
    開封第一講書人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任拦英,我火速辦了婚禮,結(jié)果婚禮上测秸,老公的妹妹穿的比我還像新娘疤估。我一直安慰自己,他們只是感情好霎冯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開白布铃拇。 她就那樣靜靜地躺著,像睡著了一般沈撞。 火紅的嫁衣襯著肌膚如雪慷荔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,729評(píng)論 1 289
  • 那天缠俺,我揣著相機(jī)與錄音显晶,去河邊找鬼贷岸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛磷雇,可吹牛的內(nèi)容都是我干的偿警。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼唯笙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼螟蒸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起崩掘,我...
    開封第一講書人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤七嫌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后呢堰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抄瑟,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凡泣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年枉疼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞋拟。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骂维,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贺纲,到底是詐尸還是另有隱情航闺,我是刑警寧澤,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布猴誊,位于F島的核電站潦刃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏懈叹。R本人自食惡果不足惜乖杠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望澄成。 院中可真熱鬧胧洒,春花似錦、人聲如沸墨状。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肾砂。三九已至列赎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間镐确,已是汗流浹背包吝。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工肛根, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漏策。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓派哲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掺喻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芭届,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容