一個神奇的控件——Android CoordinatorLayout與Behavior使用指南
介紹
CoordinatorLayout是用來協調其子view們之間動作的一個父view,而Behavior就是用來給CoordinatorLayout的子view們實現交互的。
SUM
1. CollapsingToolbarLayout_伸縮折疊工具
a. CollapsingToolbarLayout折疊或展開時,FloatingActionButton跟隨運動并且大小相應變化. CollapsingToolbarLayout是專門用來實現子布局內不同元素響應滾動細節的布局。
b. AppBarLayout是一種支持響應滾動手勢的app bar布局(比如工具欄滾出或滾入屏幕);與AppBarLayout組合的滾動布局(Recyclerview、NestedScrollView等)需要設置app:layout_behavior="@string/appbar_scrolling_view_behavior"(上面代碼中NestedScrollView控件所設置的)。沒有設置的話,AppBarLayout將不會響應滾動布局的滾動事件。。
c. CollapsingToolbarLayout和ScrollView一起使用會有滑動bug,注意要使用NestedScrollView來替代ScrollView。
Android studio中有一個Activity模板叫ScrollingActivity,它實現的就是簡單的可折疊工具欄。
ScrollingActivity的布局代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:fitsSystemWindows="true"
android:layout_height="180dp"
android:layout_width="match_parent"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!--可以include 抽取出來-->
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout></code></pre>
2.AppBarLayout的子布局有5種滾動標識
就是上面代碼CollapsingToolbarLayout中配置的app:layout_scrollFlags屬性:
- scroll:將此布局和滾動時間關聯。這個標識要設置在其他標識之前,沒有這個標識則布局不會滾動且其他標識設置無效。
- enterAlways:任何向下滾動操作都會使此布局可見。這個標識通常被稱為“快速返回”模式。
- enterAlwaysCollapsed:假設你定義了一個最小高度(minHeight)同時enterAlways也定義了,那么view將在到達這個最小高度的時候開始顯示,并且從這個時候開始慢慢展開,當滾動到頂部的時候展開完。
- exitUntilCollapsed:當你定義了一個minHeight,此布局將在滾動到達這個最小高度的時候折疊。
- snap:當一個滾動事件結束,如果視圖是部分可見的,那么它將被滾動到收縮或展開。例如,如果視圖只有底部25%顯示,它將折疊。相反,如果它的底部75%可見,那么它將完全展開。
3.CollapsingToolbarLayout的contentScrim、statusBarScrim 屬性。
- app:contentScrim設置折疊時工具欄布局的顏色
- app:statusBarScrim設置折疊時狀態欄的顏色。
默認contentScrim是colorPrimary的色值,statusBarScrim是colorPrimaryDark的色值。這個后面會用到。
4.CollapsingToolbarLayout子布局設置折疊模式,app:layout_collapseMode**
- off:這個是默認屬性,布局將正常顯示,沒有折疊的行為。
- pin:CollapsingToolbarLayout折疊后,此布局將固定在頂部。
- parallax:CollapsingToolbarLayout折疊時,此布局也會有視差折疊效果
當CollapsingToolbarLayout的子布局設置了parallax模式時,我們還可以通過
app:layout_collapseParallaxMultiplier
設置視差滾動因子,值為:0~1。
5.FloatingActionButton
FloatingActionButton這個控件通過app:layout_anchor這個設置錨定在了AppBarLayout下方。FloatingActionButton源碼中有一個Behavior方法,當AppBarLayout收縮時,FloatingActionButton就會跟著做出相應變化。關于CoordinatorLayout和Behavior,我下一篇文章會和大家一起學習。
B站很早就開源了一個彈幕引擎,還起了個狂拽酷炫吊炸天的名字叫“烈焰彈幕使 ”(一看就是二次元程序猿們的作品→_→),源碼在github上,項目名叫 DanmakuFlameMaster 。
2. 自定義CoordinatorLayout的Behavior
自定義Behavior模仿知乎

1.先看下布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="
<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"
app:layout_scrollFlags="scroll|enterAlways|snap"
android:background="?attr/colorPrimary" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/behavior_demo_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/behavior_demo_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SwipeRefreshLayout>
<!--行為,依賴于自定義Beavior-->
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:layout_marginBottom="72dp"
android:src="@android:drawable/ic_dialog_email"
app:layout_behavior="com.example.zcp.coordinatorlayoutdemo.behavior.MyFabBehavior"
android:layout_gravity="bottom|right" />
<!--行為,依賴于自定義Beavior-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:background="@color/colorPrimary"
android:gravity="center"
app:layout_behavior="com.example.zcp.coordinatorlayoutdemo.behavior.MyBottomBarBehavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#ffffff"
android:text="這是一個底欄"/>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout></code></pre>
SwipeRefreshLayout、FloatingActionButton和當做底欄的LinearLayout上有一個app:layout_behavior配置。
SwipeRefreshLayout配置的"@string/appbar_scrolling_view_behavior"是系統提供的,用來使滑動控件與AppBarLayout互動。
FloatingActionButton和底欄上配置的是我們接下來要自定義的Behavior。
先看FloatingActionButton的Behavior。
public class MyFabBehavior extends CoordinatorLayout.Behavior<View> {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private float viewY;//控件距離coordinatorLayout底部距離
private boolean isAnimate;//動畫是否在進行
public MyFabBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
if (child.getVisibility() == View.VISIBLE && viewY == 0) {
//獲取控件距離父布局(coordinatorLayout)底部距離
viewY = coordinatorLayout.getHeight() - child.getY();
}
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;//判斷是否豎直滾動
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
//大于0是向上滾動 小于0是向下滾動
if (dy >= 0 && !isAnimate && child.getVisibility() == View.VISIBLE) {
hide(child);
} else if (dy < 0 && !isAnimate && child.getVisibility() == View.GONE) {
show(child);
}
}
//隱藏時的動畫
private void hide(final View view) {
ViewPropertyAnimator animator = view.animate().translationY(viewY).setInterpolator(INTERPOLATOR).setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
isAnimate = true;
}
@Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.GONE);
isAnimate = false;
}
@Override
public void onAnimationCancel(Animator animator) {
show(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
//顯示時的動畫
private void show(final View view) {
ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
view.setVisibility(View.VISIBLE);
isAnimate = true;
}
@Override
public void onAnimationEnd(Animator animator) {
isAnimate = false;
}
@Override
public void onAnimationCancel(Animator animator) {
hide(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
}</code></pre>
邏輯并不復雜,我們通過重寫Behavior中關于嵌套滑動的兩個回調完成了FloatingActionButton的隱藏和顯示判斷及操作。
單獨出場的底欄也可以利用上面一樣的方法來設置隱藏或顯示,我的底欄是和AppBarLayout一起出場,所以我就讓底欄從屬于AppBarLayout活動。代碼如下:
public class MyBottomBarBehavior extends CoordinatorLayout.Behavior<View> {
public MyBottomBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//這個方法是說明這個子控件是依賴AppBarLayout的
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.abs(dependency.getTop());//獲取更隨布局的頂部位置
child.setTranslationY(translationY);
return true;
}
}</code></pre>
代碼量比上個還少。我們還可以通過重寫onMeasureChild()來使控件響應從屬控件的大小變化。
Behavior提供的很多,我這里用到的只是一部分,大家可以看看源碼,根據具體需求去使用。
3 .CollapsingToolbarLayout與TabLayout結合
CollapsingToolbarLayout與TabLayout組合使用的效果也不錯。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:titleEnabled="false"
android:fitsSystemWindows="true"
app:contentScrim="@color/colorPrimary"
app:statusBarScrim="@android:color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<ImageView
android:id="@+id/imageview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
android:fitsSystemWindows="true"
android:src="@drawable/girl2"/>
<View
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/gradient"
android:fitsSystemWindows="true" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="96dp"
android:minHeight="?attr/actionBarSize"
android:gravity="top"
app:layout_collapseMode="pin"
app:title="hello"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:titleMarginTop="15dp"
/>
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_gravity="bottom" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v4.view.ViewPager>
</android.support.design.widget.CoordinatorLayout></code></pre>
2. AppBarLayout
- SwipeRefreshLayout配置的"@string/appbar_scrolling_view_behavior"是系統提供的,用來使滑動控件與AppBarLayout互動。
來自:http://www.jianshu.com/p/4bb9ee0e0177