1. 程式人生 > >關於 NestedScrollView 和CoordinateLayout的互動 以及CoordinateLayout的分發

關於 NestedScrollView 和CoordinateLayout的互動 以及CoordinateLayout的分發

上一次說到,一般很少有behavior去重寫behavior.onTouchEvent和behavior.onInterceptTouchEvent方法。那麼其實我們可以直接忽略這一套流程,直接當他是正常的事件分發啦。

那麼現在模擬這麼一個情況,我們手指滑動NestedScrollView的項(設定其xml屬性:app:layout_behavior=”@string/appbar_scrolling_view_behavior”),
然後AppbarLayout隨之滾動。這是怎麼發生的呢?

如果是正常的事件分發流程,我們會到達NestedScrollView的onInterceptTouchEvent 然後是onTouchEvent

onInterceptTouchEvent:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        //mIsBeingDragged 關鍵標誌位。如果已經進入滾動狀態
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;
        }

        switch
(action & MotionEventCompat.ACTION_MASK) { case...... ..............

這個方法主要是判定是否要攔截垂直滾動的事件給自己消費,如果真的要攔截就返回true。 具體的內部判斷就不貼上了,因為不是今天的主角,大體上看過去,ACTION_MOVE 的話,如果超過一個y軸分量閾值那麼就可以判定進入了滾動狀態。ACTION_DOWN的話,如果當前滾動動畫還沒有結束(比如手指fling操作),那麼現在按下去的話也算是一種拖動了(這裡還不是很確定)

然後如果真的是上下拖動並在onInterceptTouchEvent 裡頭返回了true,事件流就跑到這個類的onTouchEvent方法內了。
onTouchEvent內部也是一個Switch case來分別處理不同型別的事件

自然的先從 ACTION_DOWN 開始
關鍵的地方出現了

....
case MotionEvent.ACTION_DOWN: {
....
 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
 .....
 }
 ....

好了主角來了startNestedScroll

    @Override
    public boolean startNestedScroll(int axes) {
        return mChildHelper.startNestedScroll(axes);
    }

跟進去

    public boolean startNestedScroll(int axes) {
        if (hasNestedScrollingParent()) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();//得到父view
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {//回撥父類的onStartNestedScroll方法!
                    mNestedScrollingParent = p;
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);//回撥父類的onNestedScrollAccepted方法!
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();//如果回撥失敗,說明不是CoordinateLayout,那麼繼續向上找
            }
        }
        return false;
    }

其實吧ViewGroup裡頭也有onStartNestedScroll和onNestedScrollAccepted方法,不過預設返回為false,而CoordinateLayout對他們進行了相應的重寫。所以如果呼叫的父view返回false,那麼
一種可能是這個父view不是CoordinateLayout,那麼就繼續沿著樹向上找
另一個可能是這個父view是CoordinateLayout,但是這個父view的子view的behavior全都沒有接受這個事件,那麼也繼續向上看看有沒有更高層級的CoordinateLayout來接收

那麼接下來就去CoordinateLayout看看實現

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

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

                lp.acceptNestedScroll(accepted);
                //在子view的lp裡頭記錄這個子view的behavior是否接受了事件!以後就靠著這個標誌位來分發接下來的流程了!
            } else {
                lp.acceptNestedScroll(false);
                //記錄子view的behaviior並沒有接受這個事件
            }
        }
        return handled;
    }

Coor依次呼叫了所有子view的behavior(如果有的話)的相應的onStartNestedScroll方法,並且返回是否至少一個behavior處理了這個事件

忽然想到了一個很好的比喻,這裡Coor起了一個類似集線器的作用,各個子view相當於連在上頭的電腦,他們整體構成一個星型拓撲的區域網結構。Coor負責轉發某臺電腦的請求給所有區域網內的連線。至於那臺電腦要如何處理這個請求那就是電腦自己的事情。

繼續看:

    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
        mNestedScrollingDirectChild = child;
        mNestedScrollingTarget = target;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            //如果在lp記錄的是不接受事件,那直接continue跳過就好了
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }
            // 如果剛才lp記錄的是接受
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
            //進一步呼叫子view的onNestedScrollAccepted
                viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
            }
        }
    }

好了回到NestedScroll的onTouchEvent,剛才說完了ACTION_DOWN,現在自然是來看ACTION_MOVE 了


case MotionEvent.ACTION_MOVE:{
    ....
    dispatchNestedPreScroll
    ...
    dispatchNestedScroll
    ...
...
}

後面的套路其實和之前的onNestedScrollAccepted都差不多,同樣也是會回撥父view的Coor的相應的方法,然後Coor在相應方法裡頭遍歷所有子view,首先檢查子view的lp是否接受了這個事件流,如果是,就接著對子view的behavior進行相應的回撥。就不羅嗦太多了

同理我們可以分析Recyclerview,在RecyclerView的onTouchEvent方法裡頭同樣的也是用startNestedScroll 來完成上面的一系列動作,思路類似,也不繼續分析了