CoordinatorLayout原理研究

一、CoordinatorLayout介紹

就像其名字一樣,CoordinatorLayout作用在于協(xié)調(diào)子 View 之間的聯(lián)動,在其出現(xiàn)之前,如要實現(xiàn)多 View 之間的聯(lián)動,需持有所有 View 的引用,難免各種事件處理、計算且高度耦合,寫法難度大。
CoordinatorLayout提供了一種清爽的方式,解決了 View 之間聯(lián)動問題。
CoordinatorLayout的基本用法網(wǎng)上遍地都是,不再贅述。主要好奇于它的強大與絲滑,想弄明白原理,參考了部分博客,然后觀摩一下源碼,在此記錄一下。

二、重中之重Behavior

抽象的來講,就是協(xié)調(diào)者布局中子 view 應(yīng)當(dāng)遵守的行為(個人理解)。
源碼中的注釋:

A {@link Behavior} that the child view should obey.

乍一聽可能懵逼,但明白其作用于原理之后,就能逐漸領(lǐng)悟這句話的意義。

1、首先Behavior作用

CoordinatorLayout(以下簡寫Co)中定義了兩個概念 Child 與Dependency,child 就是Co 中的 子 View,Dependency就是被 child 依賴的 其他 子View,當(dāng)Dependency發(fā)生某些行為(如拖動),就會通知 child 以便執(zhí)行相應(yīng)的改變。
而Behavior就提供了:

  • 依賴關(guān)系的確定
  • Dependency改變后的回調(diào)
    以經(jīng)常使用的AppBarLayout 中的ScrollingViewBehavior為例:
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            // We depend on any AppBarLayouts
            return dependency instanceof AppBarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                View dependency) {
            offsetChildAsNeeded(parent, child, dependency);
            return false;
        }

layoutDependsOn方法為依賴關(guān)系true 表示依賴,意思是聲明此 behavior 的 view依賴 Co 的自view 中所有instanceof AppBarLayout的 view。
onDependentViewChanged方法就是dependency發(fā)生改變的回調(diào),意思就是dependency也就是AppBarLayout發(fā)生改變時候執(zhí)行offsetChildAsNeeded方法(此方法無外乎是一些 View 的移動啥的)。
至此Co通過 behavior 完成了一次協(xié)調(diào)作用。

(behavior的作用遠(yuǎn)不止此,且其在 Co 中權(quán)限很高,有機會在做進(jìn)一步探討)

2、Behavior原理

1.Behavior是個啥,怎么初始化的?
點開 Co 源碼可發(fā)現(xiàn)Behavior是Co的一個抽象內(nèi)部類

 public static abstract class Behavior<V extends View> {
  ...
 }

進(jìn)一步搜索,發(fā)現(xiàn) Behavior為 Co 的LayoutParams的成員變量

 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        /**
         * A {@link Behavior} that the child view should obey.
         */
        Behavior mBehavior;
        ...
        LayoutParams(Context context, AttributeSet attrs) {
           ...
            if (mBehaviorResolved) {
                mBehavior = parseBehavior(context, attrs, a.getString(
                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
            }
           ...
}

并在LayoutParams中調(diào)用parseBehavior初始化。
(還有一種通過注解初始化,有興趣可自行了解一下:getResolvedLayoutParams)

2.分析如何建立child 與dependency依賴關(guān)系
打開 Co 源碼 command+F 搜索layoutDependsOn,順藤摸瓜看都有何處調(diào)用:
發(fā)現(xiàn)在onChildViewsChanged與LayoutParams 中的方法dependsOn中調(diào)用

        /**
         * Check if an associated child view depends on another child view of the CoordinatorLayout.
         *
         * @param parent the parent CoordinatorLayout
         * @param child the child to check
         * @param dependency the proposed dependency to check
         * @return true if child depends on dependency
         */
        boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
            return dependency == mAnchorDirectChild
                    || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
        }

onChildViewsChanged先忽略,再搜dependsOn何時調(diào)用:

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

            // Now iterate again over the other children, adding any dependencies to the graph
            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);
                }
            }
        }

        // Finally add the sorted graph list to our list
        mDependencySortedChildren.addAll(mChildDag.getSortedList());
        // We also need to reverse the result since we want the start of the list to contain
        // Views which have no dependencies, then dependent views after that
        Collections.reverse(mDependencySortedChildren);
    }

prepareChildren中可以看出通過遍歷Co 中所有的 View,把所有的依賴關(guān)系維護到mChildDag中。
mChildDag為DirectedAcyclicGraph類型可簡單理解為可以保存對應(yīng)關(guān)系的容器。

而prepareChildren在onMeasure第一行調(diào)用

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

這下就了然了,概括的說 就是 Co 在 onMeasure的時候首先會通過 behavior 把所有的 child 與Dependency的依賴關(guān)系保存起來(保存到mChildDag中)。

3.如何回調(diào)onDependentViewChanged
老方法 command+F,發(fā)現(xiàn)最終在兩處調(diào)用
onChildViewsChanged與dispatchDependentViewsChanged
后者為 public 提供給外部調(diào)用的方法,所以不用 care。

    final void onChildViewsChanged(@DispatchChangeEvent final int type) {
         ......
        for (int i = 0; i < childCount; i++) {
             ......
            // Update any behavior-dependent views for the change
            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();

                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    ......
                    switch (type) {
                        case EVENT_VIEW_REMOVED:
                            // EVENT_VIEW_REMOVED means that we need to dispatch
                            // onDependentViewRemoved() instead
                            b.onDependentViewRemoved(this, checkChild, child);
                            handled = true;
                            break;
                        default:
                            // Otherwise we dispatch onDependentViewChanged()
                            handled = b.onDependentViewChanged(this, checkChild, child);
                            break;
                    }

                    ......
                    }
                }
            }
        }

    ......      
    }

代碼過長,部分省略,果然是用腳指頭都能想到的循環(huán)遍歷調(diào)用。
繼續(xù)看onChildViewsChanged的調(diào)用:

    @Override
    public void onNestedScroll(...){
         ......  
        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

    @Override
    public void onNestedPreScroll(...){
         ......  
        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

   @Override
    public boolean onNestedFling(.....){
    ......
    }
 class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
        @Override
        public boolean onPreDraw() {
            onChildViewsChanged(EVENT_PRE_DRAW);
            return true;
        }
    }
       @Override
        public void onChildViewRemoved(View parent, View child) {
            onChildViewsChanged(EVENT_VIEW_REMOVED);
            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
            }
        }

無非是當(dāng)頁面 滑動,view繪制之前,與 view removed的時候,回回調(diào)onDependentViewChanged。這也正好解釋了 AppBarLayout 與ScrollingViewBehavior滑動聯(lián)動的原理。

三、總結(jié)

粗略的探討了CoordinatorLayout 通過 behavior 實現(xiàn)協(xié)調(diào)子 View 的基本原理。
通過自定義 behavior 可爽快的實現(xiàn)一些聯(lián)動效果。
但 通過源碼behavior在CoordinatorLayout中作用遠(yuǎn)不止此,且behavior權(quán)限很高,有機會再分析其他作用。

參考連接:
CoordinatorLayout的使用如此簡單
一步一步深入理解CoordinatorLayout

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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