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

感覺(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。