天天看點

CoordinatorLayout源碼解析

CoordinatorLayout系列文章将從淺到深帶你了解與CoordinatorLayout有關的一切

  • CoordinatorLayout實作簡單依賴滑動效果
  • CoordinatorLayout自定義Behavior實作依賴滾動布局
  • CoordinatorLayout源碼解析
源碼基于

com.android.support:design:26.1.0

,不同版本可能有所差異。

一、開始

上一篇Android CoordinatorLayout之自定義Behavior中,我們簡單介紹了

CoordinatorLayout

以及如何自定義

Behavior

。是以這次我們從源碼的角度分析

CoordinatorLayout

的内部實作機制,以便它更好的服務我們!

本文内容主要圍繞

Behavior

展開,還不了解

Behavior

的建議先看上一篇。

二、Behavior的初始化

通常使用

Behavior

是通過給

View

設定

layout_behavior

屬性,屬性值是

Behavior

的路徑,很顯然,這個屬性值最終是綁定在了

LayoutParams

上。可以猜測,和

FrameLayout

等自帶

ViewGroup

類似,

CoordinatorLayout

内部也有一個

LayoutParams

類!嗯,找到了如下代碼段:

public static class LayoutParams extends ViewGroup.MarginLayoutParams {
     
        LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            // 是否設定了layout_behavior屬性
            mBehaviorResolved = a.hasValue(
                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
            if (mBehaviorResolved) {
                mBehavior = parseBehavior(context, attrs, a.getString(
                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
            }
            a.recycle();

            if (mBehavior != null) {
                // If we have a Behavior, dispatch that it has been attached
                mBehavior.onAttachedToLayoutParams(this);
            }
           

首先判斷是否給

View

設定了

layout_behavior

屬性,如果設定了則先得到

Behavior

路徑再去解析

Behavior

,我們重點看下

parseBehavior()

方法:

static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
        if (TextUtils.isEmpty(name)) {
            return null;
        }
        // behavior的全包名路徑
        final String fullName;
        if (name.startsWith(".")) {
            // 如果設定behavior路徑不包含包名,則需要拼接包名
            fullName = context.getPackageName() + name;
        } else if (name.indexOf('.') >= 0) {
            // 設定了behavior的全包名路徑
            fullName = name;
        } else {
            // 系統内部實作,WIDGET_PACKAGE_NAME代表android.support.design.widget
            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                    ? (WIDGET_PACKAGE_NAME + '.' + name)
                    : name;
        }

        try {
            Map<String, Constructor<Behavior>> constructors = sConstructors.get();
            if (constructors == null) {
                constructors = new HashMap<>();
                sConstructors.set(constructors);
            }
            Constructor<Behavior> c = constructors.get(fullName);
            // 通過反射執行個體化behavior
            if (c == null) {
                final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
                        context.getClassLoader());
                // 指定behavior的構造函數有兩個參數
                // CONSTRUCTOR_PARAMS = new Class<?>[] {Context.class, AttributeSet.class}
                // 這也是我們自定義behavior需要兩個參數的構造函數的原因
                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
                c.setAccessible(true);
                constructors.put(fullName, c);
            }
            // 傳回behavior的執行個體
            return c.newInstance(context, attrs);
        } catch (Exception e) {
            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
        }
    }
           

parseBehavior()

方法就是使用設定的

Behavior

的路徑進一步通過反射得到

Behavior

執行個體。如果要使用該

Behavior

執行個體,可通過

CoordinatorLayout.LayoutParams

的getBehavior()方法得到:

public Behavior getBehavior() {
            return mBehavior;
        }
           

除此之外,還可以在代碼中通過

LayoutParams

View

設定

Behavior

,例如修改上一篇demo-1的behavior設定方式:

TextView title = findViewById(R.id.title);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) title.getLayoutParams();
params.setBehavior(new SampleTitleBehavior());
           

就是通過

CoordinatorLayout.LayoutParams

setBehavior()

方法完成的。

最後還有一種就是通過注解,系統的

AppBarLayout

就是用

@CoordinatorLayout.DefaultBehavior()

注解來設定

behavioe

的:

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

如果某個

View

需要固定設定某個

Behavior

,注解是個不錯的選擇,至于注解的使用時機和原理後邊會提到的。

到此,給

View

設定

Behavior

的方式和原理就基本結束了!

三、CoordinatorLayout的測量、布局

前邊已經分析了

Behavior

的初始化過程,初始化好了,總要用吧,在哪裡用呢?莫急,先看

CoordinatorLayout

在代碼層面是什麼:

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2 {
}
           

嗯,一個自定義

ViewGroup

,同時實作了

NestedScrollingParent2

接口,既然這樣,

CoordinatorLayout

必然遵循

oMeasure()

onLayout()

的執行流程。

是以我們從

CoordinatorLayout

onMeasure()

方法開始分析(隻保留了核心代碼):

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        prepareChildren();
        ensurePreDrawListener();

        final int childCount = mDependencySortedChildren.size();
        // 周遊子View,并測量大小
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // 得到給View設定的Behavior
            final Behavior b = lp.getBehavior();
            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0)) {
                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                        childHeightMeasureSpec, 0);
            }
        }
        setMeasuredDimension(width, height);
    }
           

先看

prepareChildren()

方法:

private void prepareChildren() {
        mDependencySortedChildren.clear();
        mChildDag.clear();

        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View view = getChildAt(i);

            final LayoutParams lp = getResolvedLayoutParams(view);
            lp.findAnchorView(this, view);

            mChildDag.addNode(view);

            // 按照View之間的依賴關系,存儲View
            for (int j = 0; j < count; j++) {
                if (j == i) {
                    continue;
                }
                final View other = getChildAt(j);
                if (lp.dependsOn(this, view, other)) {
                    if (!mChildDag.contains(other)) {
                        // Make sure that the other node is added
                        mChildDag.addNode(other);
                    }
                    // Now add the dependency to the graph
                    mChildDag.addEdge(other, view);
                }
            }
        }

        // mChildDag.getSortedList()會傳回一個按照依賴關系排序後的View集合
        // 被依賴的View排在前邊,沒有被依賴的在後邊
        mDependencySortedChildren.addAll(mChildDag.getSortedList());
        Collections.reverse(mDependencySortedChildren);
    }
           

很明顯

prepareChildren()

就是完成

CoordinatorLayout

中子

View

按照依賴關系的排列,被依賴的

View

排在前面,并将結果儲存在

mDependencySortedChildren

中,在每次測量前都會重新排序。

還記得我們前邊遺留了一個問題嗎?就是注解形式的

Behavior

初始化,答案就在

getResolvedLayoutParams()

中:

LayoutParams getResolvedLayoutParams(View child) {
        final LayoutParams result = (LayoutParams) child.getLayoutParams();
        if (!result.mBehaviorResolved) {
            Class<?> childClass = child.getClass();
            DefaultBehavior defaultBehavior = null;
            while (childClass != null &&
                    (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
                childClass = childClass.getSuperclass();
            }
            if (defaultBehavior != null) {
                try {
                    result.setBehavior(
                            defaultBehavior.value().getDeclaredConstructor().newInstance());
                } catch (Exception e) {
                    Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
                            " could not be instantiated. Did you forget a default constructor?", e);
                }
            }
            result.mBehaviorResolved = true;
        }
        return result;
    }
           

其實很簡單,如果沒有通過

layout_behavior

或者

java

代碼給

View

設定

Behavior

,則

LayoutParams

的成員變量

mBehaviorResolved

false

,此時如果通注解設定了

Behavior

則會在此完成

Behavior

初始化操作。可見,通過注解設定的

Behavior

被處理的優先級最低。

先跳過

ensurePreDrawListener()

方法,繼續看

onMeasure()

方法剩下的代碼,即周遊

CoordinatorLayout

的子

View

,注意這裡的子

View

從已排序的

mDependencySortedChildren

清單裡得到,先拿到

View

LayoutParams

再從中取出

Behavior

,如果

Behavior

非空,并且重寫了

onMeasureChild()

方法,則按照重寫的規則測量該子

View

,否則執行系統的預設測量。可以發現,如果有需要我們可以重寫

Behavior

onMeasureChild()

方法,攔截系統的預設onLayoutChild()方法。

繼續分析

onLayout()

方法:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();

            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }
           

其實和

onMeasure()

方法類似,周遊子

View

,确定其位置。同樣,如果有需要我們可以重寫

Behavior

onLayoutChild()

方法,攔截系統的預設的

onLayoutChild()

方法。

初見端倪,我

Behavior

就是能為所欲為,想攔截就攔截。類似的

Behavior

攔截操作後邊還會繼續講到!

四、CoordinatorLayout中的依賴、監聽

上一篇我們講到自定義

Behavior

的第一種情況是某個

View

要監聽另一個

View

的位置、尺寸等狀态的變化,需要重寫

layoutDependsOn()

onDependentViewChanged()

兩個方法,接下來剖析其中的原理。

View

狀态的變化必然導緻重繪操作,想必有一個監聽狀态變化的接口吧。上邊我們将

onMeasure()

方法時有一個

ensurePreDrawListener()

沒說,答案就在裡邊,現在來看:

void ensurePreDrawListener() {
        boolean hasDependencies = false;
        final int childCount = getChildCount();
        // 判斷是否存在依賴關系
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (hasDependencies(child)) {
                hasDependencies = true;
                break;
            }
        }
        // mNeedsPreDrawListener預設為false
        // 如果存在依賴關系
        if (hasDependencies != mNeedsPreDrawListener) {
            if (hasDependencies) {
                addPreDrawListener();
            } else {
                removePreDrawListener();
            }
        }
    }
           

大緻的作用是判斷

CoordinatorLayout

的子

View

之間是否存在依賴關系,如果存在則注冊監聽繪制的接口:

void addPreDrawListener() {
        if (mIsAttachedToWindow) {
            // Add the listener
            if (mOnPreDrawListener == null) {
                mOnPreDrawListener = new OnPreDrawListener();
            }
            final ViewTreeObserver vto = getViewTreeObserver();
            // 給ViewTreeObserver注冊一個監聽繪制的OnPreDrawListener接口
            vto.addOnPreDrawListener(mOnPreDrawListener);
        }

        // Record that we need the listener regardless of whether or not we're attached.
        // We'll add the real listener when we become attached.
        mNeedsPreDrawListener = true;
    }
           

是以第一次注冊

OnPreDrawListener

接口是在

onMeasure()

裡,并不是在

onAttachedToWindow()

裡邊。看一下

OnPreDrawListener

具體實作:

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
        @Override
        public boolean onPreDraw() {
            onChildViewsChanged(EVENT_PRE_DRAW);
            return true;
        }
    }
           

顯然核心就在

onChildViewsChanged(type)

裡邊了,這裡

type

EVENT_PRE_DRAW

代表将要繪制,另外還有兩個

type

EVENT_NESTED_SCROLL

代表嵌套滾動、

EVENT_VIEW_REMOVED

代表

View

被移除。我們來分析

onChildViewsChanged()

方法(隻保留了核心代碼):

final void onChildViewsChanged(@DispatchChangeEvent final int type) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        final Rect inset = acquireTempRect();
        final Rect drawRect = acquireTempRect();
        final Rect lastDrawRect = acquireTempRect();

        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
                // Do not try to update GONE child views in pre draw updates.
                continue;
            }

            if (type != EVENT_VIEW_REMOVED) {
                // 檢查子View狀态是否改變
                getLastChildRect(child, lastDrawRect);
                if (lastDrawRect.equals(drawRect)) {
                    continue;
                }
                // 記錄最後一次View的狀态
                recordLastChildRect(child, drawRect);
            }

            // 根據依賴關系更新View
            for (int j = i + 1; j < childCount; j++) {
                final View checkChild = mDependencySortedChildren.get(j);
                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
                final Behavior b = checkLp.getBehavior();
                // 如果Behavior不為空,并且checkChild依賴child,即重寫了layoutDependsOn
                // 則繼續執行if塊,否則目前循環到此結束!
                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    // 如果type是EVENT_PRE_DRAW,并且checkChild在嵌套滑動後已經更新
                    // 則重置标志,進行下一次循環
                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
                        checkLp.resetChangedAfterNestedScroll();
                        continue;
                    }
                    
                    final boolean handled;
                    switch (type) {
                        case EVENT_VIEW_REMOVED:
                            // 如果type是EVENT_VIEW_REMOVED,即被依賴的view被移除
                            // 則需要執行Behavior的onDependentViewRemoved()
                            b.onDependentViewRemoved(this, checkChild, child);
                            handled = true;
                            break;
                        default:
                            // 如果type是EVENT_PRE_DRAW或者EVENT_NESTED_SCROLL
                            // 并且我們重寫了Behavior的onDependentViewChanged則執行該方法
                            handled = b.onDependentViewChanged(this, checkChild, child);
                            break;
                    }

                    if (type == EVENT_NESTED_SCROLL) {
                        // 記錄在嵌套滑動後是否已經更新了View
                        checkLp.setChangedAfterNestedScroll(handled);
                    }
                }
            }
        }
    }
           

onChildViewsChanged()

方法中如果

View

之間有依賴關系,并重寫了相應

Behavior

layoutDependsOn()

方法。則會執行

Behavior

onDependentViewChanged()

onDependentViewRemoved()

方法。這也就解釋了上一篇中第一種情況的原理,可見這裡起關鍵作用的還是

Behavior

五、NestedScrolling機制

可能你沒聽過這個概念,但你可能已經使用過它了,例如

CoordinatorLayout

嵌套

RecyclerView

的布局、

NestedScrollView

等,都有用到了這個原理。

NestedScrolling

提供了一套父

View

和子

View

嵌套滑動的互動機制,前提條件是父

View

需要實作

NestedScrollingParent

接口,子

View

需要實作

NestedScrollingChild

接口。按照

NestedScrolling[Parent|Child]

接口的要求(可檢視接口的注釋),實作該接口的

View

需要建立一個

NestedScrolling[Parent|Child]Helper

幫助類執行個體來輔助子

View

和父

View

的互動。

NestedScrollingParent 與 NestedScrollingChild 接口方法是怎樣回調的

執行流程

  • 在 Action_Down 的時候,Scrolling child 會調用 startNestedScroll 方法,通過 childHelper 回調 Scrolling Parent 的 startNestedScroll 方法
  • 在 Action_move 的時候,Scrolling Child 要開始滑動的時候,會調用dispatchNestedPreScroll 方法,通過 ChildHelper 詢問 Scrolling Parent 是否要先于 Child 進行 滑動,若需要的話,會調用 Parent 的 onNestedPreScroll 方法,協同 Child 一起進行滑動
  • 當 ScrollingChild 滑動完成的時候,會調用 dispatchNestedScroll 方法,通過 ChildHelper 詢問 Scrolling Parent 是否需要進行滑動,需要的話,會 調用 Parent 的 onNestedScroll 方法
  • 在 Action_down,Action_move 的時候,會調用 Scrolling Child 的stopNestedScroll ,通過 ChildHelper 詢問 Scrolling parent 的 stopNestedScroll 方法。
  • 在 Action_down,Action_move 的時候,會調用 Scrolling Child 的stopNestedScroll ,通過 ChildHelper 詢問 Scrolling parent 的 stopNestedScroll 方法。
  • 如果需要處理 Fling 動作,我們可以通過 VelocityTrackerCompat 獲得相應的速度,并在 Action_up 的時候,調用 dispatchNestedPreFling 方法,通過 ChildHelper 詢問 Parent 是否需要先于 child 進行 Fling 動作
  • 在 Child 處理完 Fling 動作時候,如果 Scrolling Parent 還需要處理 Fling 動作,我們可以調用 dispatchNestedFling 方法,通過 ChildHelper ,調用 Parent 的 onNestedFling 方法

先後構造一個符合

NestedScrolling

機制的場景,前邊我們已經提到了

CoordinatorLayout

實作了

NestedScrollingParent2

接口,

NestedScrollingParent2

又繼承自

NestedScrollingParent

,是以

CoordinatorLayout

做為父

View

的條件是滿足的;其實

RecyclerView

實作了

NestedScrollingChild2

接口,

NestedScrollingChild2

又繼承自

NestedScrollingChild

接口也滿足作為子

View

的條件。

NestedScrolling[Parent|Child]2

接口可以看作是對

NestedScrolling[Parent|Child]

接口的擴充,本質和作用類似。

可以用上一篇自定義

Behavior

的第二種情況的例子來分析:在

CoordinatorLayout

裡邊嵌套一個

RecyclerView

TextView

TextView

跟随

RecyclerView

的滑動來移動。根據上邊的分析,在

CoordinatorLayout

裡應該有一個

NestedScrollingParentHelper

的執行個體,在

RecyclerView

裡應該有一個

NestedScrollingChildHelper

的執行個體。

從哪裡開始分析呢?因為例子的效果是從

RecyclerView

的滑動開始的,是以就從

RecyclerView

開始吧!滑動必然先進行事件的分發,是以先看它的

onInterceptTouchEvent()

方法(隻保留核心代碼)是否有我們想要的東西:

@Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        final int action = e.getActionMasked();
        final int actionIndex = e.getActionIndex();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
                break;
        }
    }
           

發現了一個

startNestedScroll()

方法,和之前自定義

Behavior

重寫的

onStartNestedScroll()

方法有點像哦!目測找對地方了,繼續看

startNestedScroll()

方法:

@Override
    public boolean startNestedScroll(int axes, int type) {
        return getScrollingChildHelper().startNestedScroll(axes, type);
    }
           

原來是重寫了

NestedScrollingParent2

接口的方法,

getScrollingChildHelper()

做了什麼呢?

private NestedScrollingChildHelper getScrollingChildHelper() {
        if (mScrollingChildHelper == null) {
            mScrollingChildHelper = new NestedScrollingChildHelper(this);
        }
        return mScrollingChildHelper;
    }
           

就是執行個體化上邊提到的

NestedScrollingChildHelper

,是以

startNestedScroll(axes, type)

就是該幫助類的方法:

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }
           

這裡插一段,

isNestedScrollingEnabled()

代表是否啟用了嵌套滑動,隻有啟用了嵌套滑動,事件才能繼續分發。這個是在哪裡設定的呢?

RecyclerView

的構造函數中:

public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        // Re-set whether nested scrolling is enabled so that it is set on all API levels
        setNestedScrollingEnabled(nestedScrollingEnabled);
    }
           

最終調用了

NestedScrollingChildHelper

setNestedScrollingEnabled()

方法:

@Override
    public void setNestedScrollingEnabled(boolean enabled) {
        getScrollingChildHelper().setNestedScrollingEnabled(enabled);
    }
           

是以我們自定義的實作

NestedScrollingChild

接口的

View

,也需要設定

setNestedScrollingEnabled(true)

,具體方法了

RecyclerView

中類似。好了插播結束,繼續往下看。

mView

是什麼呢?還記得上邊建立

mScrollingChildHelper

的構造函數嗎?就是:

public NestedScrollingChildHelper(@NonNull View view) {
        mView = view;
    }
           

是以

mView

就是

RecyclerView

,則

p

就應該是

CoordinatorLayout

,看下

if

條件

ViewParentCompat.onStartNestedScroll()

裡邊的實作:

public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
        }
        return false;
    }
           

看到了熟悉的

NestedScrollingParent2

,因為

CoordinatorLayout

實作了該接口,這也更加确定了

parent

就是

CoordinatorLayout

,是以

((NestedScrollingParent2) parent).onStartNestedScroll(child, target, nestedScrollAxes, type)

就回到了

CoordinatorLayout

去執行:

@Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;

        final int childCount = getChildCount();
        // 周遊CoordinatorLayout的子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();
            // 如果目前View有Behavior,則調用其onStartNestedScroll()方法。
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }
           

該方法就是周遊

CoordinatorLayout

的子

View

,如果有

Behavior

則調用它的

onStartNestedScroll

方法,如果傳回

true

,則

Behavior

就攔截了這次事件,進一步可以更新對應的

View

狀态。

到這裡一個

NestedScrolling

機制互動的流程就走完了,先簡單總結一下,事件從

RecyclerView

開始到

NestedScrollingChildHelper

經過

ViewParentCompat

回到了

CoordinatorLayout

最後被

Behavior

處理掉了。

之前我們還重寫了

Behavior

onNestedPreScroll()

方法,來處理

RecyclerView

的滑動事件,既然是滑動,那肯定逃不出

onTouchEvent()

ACTION_MOVE

事件(隻保留核心代碼):

@Override
    public boolean onTouchEvent(MotionEvent e) {
        final int action = e.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                final int index = e.findPointerIndex(mScrollPointerId);
                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;

                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
                    dx -= mScrollConsumed[0];
                    dy -= mScrollConsumed[1];
                    vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
                    // Updated the nested offsets
                    mNestedOffsets[0] += mScrollOffset[0];
                    mNestedOffsets[1] += mScrollOffset[1];
                }
            } break;
        return true;
    }
           

有一個

dispatchNestedPreScroll()

方法,繼續跟進,中間的流程和上的類似,可以自行打斷點跟一遍,我們重點看一下回到

CoordinatorLayout

中的

onNestedPreScroll()

方法:

@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
        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(type)) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);

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

同樣是周遊子

View

,如果可以則執行

View

對應

Behavior

onNestedPreScroll()

方法。當然這不是重點,這裡有個

mTempIntPair

數組,對應

Behavior

onNestedPreScroll()

方法的

consumed

參數,是以之前我們寫

consumed[1] = dy

,實際是給

mTempIntPair

複制,最終讓父

View

CoordinatorLayout

消費掉事件,也就是你滑動的是

RecyclerView

但實際上

CoordinatorLayout

在整體移動!

是以在

NestedScrolling

機制中,當實作了

NestedScrollingChild

接口的子

View

滑動時,現将自己滑動的

dx

dy

傳遞給實作了

NestedScrollingParent

接口的父

View

,讓

View

先決定是否要消耗相應的事件,父

View

可以消費全部事件,如果父

View

消耗了部分,則剩下的再由子

View

處理。

六、CoordinatorLayout的TouchEvent

CoordinatorLayout

作為一個自定義

ViewGroup

,必然會重寫

onInterceptTouchEvent()

onTouchEvent

來進行事件的攔截和處理。結合之前多次的分析結果,可以猜測這兩個方法最終也由相應子

View

Behavior

來處理!

看一下這兩個方法:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        MotionEvent cancelEvent = null;
        final int action = ev.getActionMasked();
        // 重置Behavior的相關記錄,為下次事件做準備
        if (action == MotionEvent.ACTION_DOWN) {
            resetTouchBehaviors();
        }
        // 是否攔截目前事件是由performIntercept()的傳回值決定的
        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
        if (cancelEvent != null) {
            cancelEvent.recycle();
        }
        // 同樣是重置Behavior
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            resetTouchBehaviors();
        }
        return intercepted;
    }
           
@Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        boolean cancelSuper = false;
        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();
        // mBehaviorTouchView不為空,表示某個View的Behavior正在處理目前事件
        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
            // 繼續由相應的Behavior處理目前事件
            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
            final Behavior b = lp.getBehavior();
            if (b != null) {
                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
            }
        }
        return handled;
    }
           

它們都調用了一個共同的方法

performIntercept()

private boolean performIntercept(MotionEvent ev, final int type) {
        boolean intercepted = false;
        boolean newBlock = false;
        MotionEvent cancelEvent = null;
        final int action = ev.getActionMasked();

        final List<View> topmostChildList = mTempList1;
        // 按照 z-order 排序,讓最頂部的View先被處理
        getTopSortedChildren(topmostChildList);
        final int childCount = topmostChildList.size();
        for (int i = 0; i < childCount; i++) {
            final View child = topmostChildList.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior b = lp.getBehavior();
            // 如果已經有Behavior攔截了事件或者是新的攔截,并且不是ACTION_DOWN
            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
                if (b != null) {
                    if (cancelEvent == null) {
                        final long now = SystemClock.uptimeMillis();
                        cancelEvent = MotionEvent.obtain(now, now,
                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                    }
                     // 發送cancelEvent給攔截了事件之後的其它子View的Behavior
                    switch (type) {
                        case TYPE_ON_INTERCEPT:
                            b.onInterceptTouchEvent(this, child, cancelEvent);
                            break;
                        case TYPE_ON_TOUCH:
                            b.onTouchEvent(this, child, cancelEvent);
                            break;
                    }
                }
                continue;
            }
            // 如果目前事件還沒被攔截,則先由目前周遊到的子View的Behavior處理
            if (!intercepted && b != null) {
                switch (type) {
                    case TYPE_ON_INTERCEPT:
                        intercepted = b.onInterceptTouchEvent(this, child, ev);
                        break;
                    case TYPE_ON_TOUCH:
                        intercepted = b.onTouchEvent(this, child, ev);
                        break;
                }
                if (intercepted) {
                    // 記錄要處理目前事件的View
                    mBehaviorTouchView = child;
                }
            }

            // Don't keep going if we're not allowing interaction below this.
            // Setting newBlock will make sure we cancel the rest of the behaviors.
            final boolean wasBlocking = lp.didBlockInteraction();
            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
            newBlock = isBlocking && !wasBlocking;
            if (isBlocking && !newBlock) {
                // Stop here since we don't have anything more to cancel - we already did
                // when the behavior first started blocking things below this point.
                break;
            }
        }

        topmostChildList.clear();

        return intercepted;
    }
           

相關的說明都在注釋裡了,是以

CoordinatorLayout

的事件處理,還是優先的交給子

View

Behavior

來完成。

七、小結

到此,

CoordinatorLayout

的幾個重要的點就分析完了,其實核心還是

CoordinatorLayout

Behavior

之間的關系,理清了這個也就明白為什麼

Behavior

可以實作攔截一切的效果!對自定義

Behavior

也是有很大幫助的。可以發現

CoordinatorLayout

最終都是将各種處理優先交給了

Behavior

來完成,是以

CoordinatorLayout

更像是

Behavior

的代理!