CoordinatorLayout原理研究

一、CoordinatorLayout介紹

就像其名字一樣噩茄,CoordinatorLayout作用在于協(xié)調(diào)子 View 之間的聯(lián)動(dòng)添怔,在其出現(xiàn)之前,如要實(shí)現(xiàn)多 View 之間的聯(lián)動(dòng)弟劲,需持有所有 View 的引用,難免各種事件處理姥芥、計(jì)算且高度耦合兔乞,寫法難度大。
CoordinatorLayout提供了一種清爽的方式凉唐,解決了 View 之間聯(lián)動(dòng)問題庸追。
CoordinatorLayout的基本用法網(wǎng)上遍地都是,不再贅述台囱。主要好奇于它的強(qiáng)大與絲滑淡溯,想弄明白原理,參考了部分博客玄坦,然后觀摩一下源碼血筑,在此記錄一下绘沉。

二煎楣、重中之重Behavior

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

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

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

1困曙、首先Behavior作用

CoordinatorLayout(以下簡寫Co)中定義了兩個(gè)概念 Child 與Dependency表伦,child 就是Co 中的 子 View,Dependency就是被 child 依賴的 其他 子View慷丽,當(dāng)Dependency發(fā)生某些行為(如拖動(dòng))蹦哼,就會(huì)通知 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ā)生改變時(shí)候執(zhí)行offsetChildAsNeeded方法(此方法無外乎是一些 View 的移動(dòng)啥的)锄俄。
至此Co通過 behavior 完成了一次協(xié)調(diào)作用局劲。

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

2鱼填、Behavior原理

1.Behavior是個(gè)啥,怎么初始化的毅戈?
點(diǎn)開 Co 源碼可發(fā)現(xiàn)Behavior是Co的一個(gè)抽象內(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何時(shí)調(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)系維護(hù)到mChildDag中。
mChildDag為DirectedAcyclicGraph類型可簡單理解為可以保存對應(yīng)關(guān)系的容器塑陵。

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

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

這下就了然了感憾,概括的說 就是 Co 在 onMeasure的時(shí)候首先會(huì)通過 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)頁面 滑動(dòng)扮碧,view繪制之前趟章,與 view removed的時(shí)候,回回調(diào)onDependentViewChanged慎王。這也正好解釋了 AppBarLayout 與ScrollingViewBehavior滑動(dòng)聯(lián)動(dòng)的原理蚓土。

三、總結(jié)

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绷耍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鲜侥,更是在濱河造成了極大的恐慌褂始,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件描函,死亡現(xiàn)場離奇詭異病袄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赘阀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門益缠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人基公,你說我怎么就攤上這事幅慌。” “怎么了轰豆?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵胰伍,是天一觀的道長。 經(jīng)常有香客問我酸休,道長骂租,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任斑司,我火速辦了婚禮渗饮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宿刮。我一直安慰自己互站,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布僵缺。 她就那樣靜靜地躺著胡桃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕潮。 梳的紋絲不亂的頭發(fā)上翠胰,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音自脯,去河邊找鬼之景。 笑死,一個(gè)胖子當(dāng)著我的面吹牛冤今,可吹牛的內(nèi)容都是我干的闺兢。 我是一名探鬼主播茂缚,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戏罢,長吁一口氣:“原來是場噩夢啊……” “哼屋谭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起龟糕,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤桐磁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后讲岁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體我擂,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年缓艳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了校摩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阶淘,死狀恐怖衙吩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溪窒,我是刑警寧澤坤塞,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站澈蚌,受9級(jí)特大地震影響摹芙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宛瞄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一浮禾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧份汗,春花似錦伐厌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轩猩,卻和暖如春卷扮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背均践。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工晤锹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彤委。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓鞭铆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子车遂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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