1CoordinatorLayout與behavior入門(mén)

前言

閑來(lái)無(wú)事,掏出AS熟練的new一個(gè)project,發(fā)現(xiàn)默認(rèn)出來(lái)的activity點(diǎn)擊之后有如下動(dòng)畫(huà)

fab 和snackbar

感覺(jué)很神奇,于是分析以下,這是怎么做到的?
這個(gè)activity的style是@style/AppTheme.NoActionBar,布局如下所示,這是目前android比較推崇的做法,基本理念就是脫離actionbar,在自己的布局里寫(xiě)toolbar,這樣會(huì)提高更大的自由度,toolbar就變成了一個(gè)普通的view,我想放哪里放哪里,想怎么放怎么放(其實(shí)默認(rèn)actionbar也是toolbar實(shí)現(xiàn)的,view層次結(jié)構(gòu)可以參考AppCompatActivity的View樹(shù))。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.fish.a2.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

這一個(gè)xml中,CoordinatorLayout、FloatingActionButton、AppBarLayout都是陌生的。FloatingActionButton就是右下角的帶陰影圓形按鈕,可以簡(jiǎn)單看做一個(gè)ImageButton,如果對(duì)此控件有興趣,可以閱讀http://blog.csdn.net/lmj623565791/article/details/46678867,此文內(nèi)鴻洋大神還自己實(shí)現(xiàn)了陰影效果。
我們?cè)賮?lái)看自動(dòng)生成的部分代碼,F(xiàn)loatingActionButton(后邊簡(jiǎn)稱(chēng)fab)的點(diǎn)擊就是彈出一個(gè)Snackbar,又出來(lái)一個(gè)新玩意,Snackbar可以看做是Toast的升級(jí)版本,沒(méi)什么高深的,下邊代碼的意思就是點(diǎn)擊fab彈出一個(gè)Snackbar,Snackbar像toast一樣幾秒后就消失。

      FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_SHORT)
                        .setAction("Action", null).show();
            }
        });

這些看起來(lái)都很簡(jiǎn)單,但是Snackbar彈出的時(shí)候,為何fab為何為往上跑呢?然后過(guò)了幾秒鐘,Snackbar消失了,fab又自動(dòng)往下滑了,這2個(gè)動(dòng)畫(huà)是怎么做到的?從代碼里看不出任何端倪.

CoordinatorLayout

先簡(jiǎn)單介紹下CoordinatorLayout,看官方文檔
CoordinatorLayout is a super-powered FrameLayout.

CoordinatorLayout is intended for two primary use cases:

1.As a top-level application decor or chrome layout
2.As a container for a specific interaction with one or more child views

By specifying Behaviors for child views of a CoordinatorLayout you can provide many different interactions within a single parent and those views can also interact with one another. View classes can specify a default behavior when used as a child of a CoordinatorLayout using the DefaultBehavior annotation.

Behaviors may be used to implement a variety of interactions and additional layout modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons that stick to other elements as they move and animate.

CoordinatorLayout是個(gè)超級(jí)強(qiáng)大的FrameLayout(其實(shí)他并不繼承FrameLayout,只是里面的view布局和FrameLayout一樣,一層層疊上去)

CoordinatorLayout主要有2種用法
1.作為app的rootview(contentview的child)
2.作為一個(gè)容器,包含幾個(gè)view,這些view有特殊行為,互相作用

給CoordinatorLayout的子view指定behavior可以讓他們互相作用互相依賴(lài)

behavior

概述

Behavior是一種新的view關(guān)系描述,CoordinatorLayout的一個(gè)子view可以依賴(lài)于另一個(gè)子view做出響應(yīng),比如有子view A和B,A依賴(lài)于B,一旦B發(fā)生變化(比如大小位置可見(jiàn)性改變),A就可以知道并且響應(yīng),這其實(shí)就是觀察者模式。一個(gè)子view可以依賴(lài)于多個(gè)子view,比如A可以依賴(lài)于B,C這個(gè)依賴(lài)關(guān)系會(huì)在一個(gè)類(lèi)內(nèi)寫(xiě)明,這個(gè)類(lèi)是CoordinatorLayout.Behavior的子類(lèi),可以自定義。

例子解釋

還是有點(diǎn)難以理解,看上文的實(shí)際例子吧。
fab會(huì)因?yàn)镾nackbar的彈出而上移,隨著Snackbar的消失而下移,是因?yàn)閒ab依賴(lài)于Snackbar。 這種依賴(lài)關(guān)系由FloatingActionButton內(nèi)部的Behavior類(lèi)決定的。
Behavior內(nèi)有如下代碼,看layoutDependsOn代碼,dependency是SnackbarLayout就返回true(SNACKBAR_BEHAVIOR_ENABLED前提下),這就說(shuō)明了fab依賴(lài)于SnackbarLayout,一旦SnackbarLayout發(fā)生變化,fab就會(huì)立刻知道并作出反應(yīng)。

    @Override
        public boolean layoutDependsOn(CoordinatorLayout parent,
                FloatingActionButton child, View dependency) {
            // We're dependent on all SnackbarLayouts (if enabled)
            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
        }

做出什么反應(yīng)呢?來(lái)看onDependentViewChanged的代碼,主要調(diào)用updateFabTranslationForSnackbar,注意updateFabTranslationForSnackbar 的L34、L35,這里觸發(fā)了平移動(dòng)畫(huà)。這就是為什么snackbar出現(xiàn)或消失的時(shí)候,fab會(huì)有平移動(dòng)畫(huà)。


       @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
                updateFabTranslationForSnackbar(parent, child, dependency);
            } 
            return false;
        }
        
       private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
                final FloatingActionButton fab, View snackbar) {
            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
            if (mFabTranslationY == targetTransY) {
                // We're already at (or currently animating to) the target value, return...
                return;
            }

            final float currentTransY = ViewCompat.getTranslationY(fab);

            // Make sure that any current animation is cancelled
            if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
                mFabTranslationYAnimator.cancel();
            }

            if (fab.isShown()
                    && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
                // If the FAB will be travelling by more than 2/3 of it's height, let's animate
                // it instead
                if (mFabTranslationYAnimator == null) {
                    mFabTranslationYAnimator = ViewUtils.createAnimator();
                    mFabTranslationYAnimator.setInterpolator(
                            AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                    mFabTranslationYAnimator.setUpdateListener(
                            new ValueAnimatorCompat.AnimatorUpdateListener() {
                                @Override
                                public void onAnimationUpdate(ValueAnimatorCompat animator) {
                                    ViewCompat.setTranslationY(fab,
                                            animator.getAnimatedFloatValue());
                                }
                            });
                }
                //這里制造動(dòng)畫(huà)來(lái)完成上移或者下移
                mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
                mFabTranslationYAnimator.start();
            } else {
                // Now update the translation Y
                ViewCompat.setTranslationY(fab, targetTransY);
            }

            mFabTranslationY = targetTransY;
        }

我們?cè)倬唧w分析下,為何snackbar出現(xiàn)的時(shí)候,fab上移,而snackbar消失的時(shí)候fab下移呢?
在snackbar出現(xiàn)的時(shí)候,會(huì)調(diào)用onDependentViewChanged()然后調(diào)用updateFabTranslationForSnackbar(),這里重點(diǎn)是L34代碼,意思就是從currentTransY平移到targetTransY,關(guān)注下這2個(gè)變量。currentTransY怎么來(lái)的,看L9就是fab本身的TranslationY,那targetTransY呢?看L3,由getFabTranslationYForSnackbar得到。

所以看一下getFabTranslationYForSnackbar的代碼,L5獲取CoordinatorLayout內(nèi)部被依賴(lài)的子view,在這個(gè)CoordinatorLayout內(nèi)只有fab依賴(lài)于Snackbar.SnackbarLayout,所以Snackbar.SnackbarLayout是被依賴(lài)的子view,也只有這一個(gè)??碙10,此時(shí)view就是Snackbar.SnackbarLayout ,minOffset為0,ViewCompat.getTranslationY(view) 為0,所以最終得到的minOffset就是- view.getHeight(),其實(shí)就是snackbar的高度的相反數(shù)。我們假設(shè)-200好了。再回到updateFabTranslationForSnackbar內(nèi),此時(shí)targetTransY為-200,而currentTransY為0,那會(huì)發(fā)生什么?從0上移到-200,所以snackbar出現(xiàn)的時(shí)候,fab做了一個(gè)上移動(dòng)畫(huà)。

       private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
                FloatingActionButton fab) {
            float minOffset = 0;
            //獲取被依賴(lài)的子view
            final List<View> dependencies = parent.getDependencies(fab);
            for (int i = 0, z = dependencies.size(); i < z; i++) {
                final View view = dependencies.get(i);
                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
                //重點(diǎn)是這句代碼
                    minOffset = Math.min(minOffset,
                            ViewCompat.getTranslationY(view) - view.getHeight());
                }
            }

            return minOffset;
        }

下面我們?cè)倏梢苑治鰏nackbar消失的時(shí)候的場(chǎng)景,注意這里稍有不同,snackbar 出現(xiàn)調(diào)用了onDependentViewChanged,但是snackbar消失調(diào)用的是onDependentViewRemoved, 但最終調(diào)的還是updateFabTranslationForSnackbar ,殊途同歸啊,此時(shí)getFabTranslationYForSnackbar內(nèi)dependencies為空(因?yàn)閟nackbar 不見(jiàn)了),所以最終返回0,所以targetTransY為0,而currentTransY位-200,所以就從-200下移到了0,下移就是這么產(chǎn)生的。

    @Override
        public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
                View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
                updateFabTranslationForSnackbar(parent, child, dependency);
            }
        }

snackbar出現(xiàn)回調(diào)到onDependentViewChanged,然后調(diào)用updateFabTranslationForSnackbar完成上移。
snackbar消失回調(diào)到onDependentViewRemoved,然后調(diào)用updateFabTranslationForSnackbar完成上移。

總結(jié)

簡(jiǎn)單的說(shuō)CoordinatorLayout是容器,behavior是核心。behavior封裝了CoordinatorLayout的子view隨著其他子view的變化而變化的規(guī)則,主要是通過(guò)layoutDependsOn和onDependentViewChanged這2個(gè)方法。再看看CoordinatorLayout的名字,就是協(xié)調(diào)布局的意思,協(xié)調(diào)子view之間的關(guān)系,被協(xié)調(diào)的2個(gè)view必須是CoordinatorLayout的直接子view

dependency概念

fab依賴(lài)于Snackbar.SnackbarLayout,那我們就可以說(shuō)fab的dependency是Snackbar.SnackbarLayout。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容