CoordinatorLayout是在 Google IO/15 大會(huì)發(fā)布的控件,常常與AppBarLayout和CollapsingToolbarLayout一起使用,用于打造各種炫酷效果的頂部欄。
使用前須知
CoordinatorLayout目前我僅在RecyclerView、NestedScrollView、ViewPager上面成功通過設(shè)置behavior來監(jiān)聽滑動(dòng)事件,ListView和ScrollView都是不行的。什么原因,我們來看下CoordinatorLayout的源碼:

可以看到CoordinatorLayout繼承了NestedScrollingParent接口,那么再看下RecyclerView和NestedScrollView吧:


這樣子想必都猜到是什么回事了。
Android 在發(fā)布 Lollipop版本之后,Google為Android的滑動(dòng)機(jī)制提供了NestedScrolling機(jī)制,為了給用戶帶來更好的體驗(yàn)。而CoordinatorLayout正是基于NestedScrolling來開發(fā)的。
NestedScrolling機(jī)制可以簡(jiǎn)單理解為child將它的一些滑動(dòng)事件交給了Parent去處理了。ListView和ScrollView無法和CoordinatorLayout配合使用的原因就是因?yàn)樗鼈儧]繼承這個(gè)NestedScrollingChild接口。那么ViewPager可以配合使用的原因是什么?查看ViewPager的繼承關(guān)系會(huì)發(fā)現(xiàn)它沒有繼承這個(gè)Child接口,但是我們?nèi)タ聪耾nInterceptTouchEvent方法里面,會(huì)看到有下面的注釋:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//省去部分代碼
if (dx != 0 && !isGutterDrag(mLastMotionX, dx)
&& canScroll(this, false, (int) dx, (int) x, (int) y)) {
// Nested view has scrollable area under this point. Let it be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
}
..............
翻譯過來大意是這個(gè)方向有嵌套的View在滾動(dòng),就讓它在那里處理吧。換言之,就是ViewPager檢測(cè)到NestedScrolling相關(guān)的事件觸發(fā),就不做攔截,讓外層的CoordinatorLayout成功監(jiān)聽到ViewPager嵌套的RecyclerView或NestedScrollView的滑動(dòng)事件。
如果你對(duì)NestedScrolling機(jī)制很感興趣,這里推薦下面的文章:
Android NestedScrolling機(jī)制完全解析 帶你玩轉(zhuǎn)嵌套滑動(dòng)
Android 嵌套滑動(dòng)機(jī)制(NestedScrolling)
Android NestedScrolling 實(shí)戰(zhàn)
接下來是本文的重點(diǎn)了。
AppBarLayout
剛才已經(jīng)提過了,CoordinatorLayout是經(jīng)常與AppBarLayout配合使用的,所以很有必要搞明白AppBarLayout到底能做哪些事情。
AppBarLayout它繼承LinearLayout,布局方向默認(rèn)為垂直方向,它可以讓你定制RecyclerView、NestedScrollView、ViewPager的滾動(dòng)手勢(shì)發(fā)生變化時(shí),其內(nèi)部的子View實(shí)現(xiàn)某個(gè)指定的動(dòng)作。
目前布局代碼如下,這里先用RecyclerView來說明:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.fritz.layout.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_scrollFlags="待補(bǔ)充"
app:title="CoordinatorLayout" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
這里需要注意的是app:layout_behavior="@string/appbar_scrolling_view_behavior"這個(gè)屬性,就是這個(gè)屬性讓AppBarLayout與RecyclerView之間動(dòng)作相互依賴,這也是Google幫我們定義好了一個(gè)behavior,我們直接用就是了。
AppBarLayout內(nèi)部的子View通過在布局中加app:layout_scrollFlags設(shè)置執(zhí)行的動(dòng)作,那么app:layout_scrollFlags可以設(shè)置哪些動(dòng)作呢?我們一一來看吧,下面的View值的是AppBarLayout里面的子View:
- 1.scroll: 想要當(dāng)滑出屏幕的時(shí)候View都必須設(shè)置這個(gè)flag,沒有設(shè)置這個(gè)flag的View將被固定在屏幕頂部,其他的flag屬性也無法生效

- 2.enterAlways:flag設(shè)為scroll|enterAlways的View,當(dāng)RecyclerView上的手指往上滾動(dòng)時(shí),該View會(huì)直接往上滾動(dòng)移出屏幕;當(dāng)RecyclerView上的手指往下滾動(dòng)時(shí),該View會(huì)立刻往下滾動(dòng)恢復(fù)原位

- 3.exitUntilCollapsed:flag設(shè)為scroll|exitUntilCollapsed的View,當(dāng)這個(gè)View要往上逐漸滑出屏幕時(shí),會(huì)一直往上滑動(dòng),直到剩下的的高度達(dá)到它的最小高度后,再響應(yīng)RecyclerView的內(nèi)部滑動(dòng)事件。請(qǐng)注意,如果沒有設(shè)置android:minHeight的話,那么這個(gè)屬性是不會(huì)生效的。這里我將toolbar的android:layout_height設(shè)為200dp,android:minHeight設(shè)為50dp,效果如下:

- 4.enterAlwaysCollapsed:這個(gè)是enterAlways的附加選項(xiàng),要跟enterAlways一起使用,同時(shí)需要設(shè)置android:minHeight屬性,否則不起作用的。它是指,當(dāng)RecyclerView上的手指往下滾動(dòng)時(shí),View首先是enterAlways效果,當(dāng)View的高度達(dá)到最小高度時(shí),View就暫時(shí)不去往下滾動(dòng),直到某個(gè)可滾動(dòng)View滑動(dòng)到頂部并停止滑動(dòng)時(shí),View再繼續(xù)往下滑動(dòng),直到滑到View的頂部結(jié)束。為了更好展示這個(gè)效果,我將toolbar的android:layout_height設(shè)為200dp,android:minHeight設(shè)為50dp,效果如下:

- 5.snap:這個(gè)和上面的一樣,不會(huì)單獨(dú)使用,常與exitUntilCollapsed配合使用。在滾動(dòng)結(jié)束后,如果View只是部分可見,它將滑動(dòng)到最近的邊界。比如,如果View的底部只有25%可見時(shí)停止滑動(dòng),它將繼續(xù)滑動(dòng)直至離開屏幕,而如果底部有75%可見時(shí)停止滑動(dòng),它將繼續(xù)滑動(dòng)到完全顯示。

好了,AppBarLayout相關(guān)功能到這里說完了,其實(shí)用起來還是蠻簡(jiǎn)單的。接著,到另一位主角CollapsingToolbarLayout的出場(chǎng)了。
CollapsingToolbarLayout
CollapsingToolbarLayout是用來對(duì)Toolbar進(jìn)行再次包裝的FrameLayout,主要是用于實(shí)現(xiàn)折疊(個(gè)人覺得收縮比較貼實(shí))的AppBar效果。它需要放在AppBarLayout布局里面,并且作為AppBarLayout的直接子View。它常用的有以下屬性:
- 1.app:contentScrim:CollapsingToolbarLayout折疊(收縮)完成后的背景顏色
- 2.app:expandedTitleMarginXXX:設(shè)置CollapsingToolbarLayout展開時(shí)候title的Margin
- 3.app:layout_collapseMode:這個(gè)是讓CollapsingToolbarLayout的子View設(shè)置的,共有3個(gè)flag可以使用,它們分別為:
1) parallax:設(shè)置為這個(gè)flag時(shí)為視差模式,也就是子View折疊模式。在RecyclerView等View在滑動(dòng)時(shí),子View以視差(折疊)的方式來滑動(dòng)
2)pin:設(shè)置為這個(gè)flag時(shí)為固定模式,在折疊的時(shí)候會(huì)固定在頂端
3)none:設(shè)置為這個(gè)flag時(shí)為沒有效果,RecyclerView往上滑動(dòng)的時(shí)候,子View會(huì)首先被固定并推出去 - 4.app:layout_collapseParallaxMultiplier:設(shè)置視差滾動(dòng)因子,值為:0~1,值越小,折疊效果就越明顯,需要搭配app:layout_collapseMode的parallax模式使用
好了,了解上面這些知識(shí)后,我們來做一個(gè)炫酷的頂部欄吧,這里我使用了Databinding來做了一個(gè)全新的布局:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="adapter"
type="android.support.v7.widget.RecyclerView.Adapter" />
<variable
name="manager"
type="android.support.v7.widget.RecyclerView.LayoutManager" />
</data>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.fritz.layout.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="200dp"
app:contentScrim="?attr/colorPrimaryDark"
app:expandedTitleMarginEnd="20dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:setCollapsedTitleTextColor="@{@color/color_while}"
app:setExpandedTitleTextColor="@{@color/colorAccent}">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_bg"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_action_back"
app:title="OverLoad" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:setAdapter="@{adapter}"
app:setLayoutManager="@{manager}" />
</android.support.design.widget.CoordinatorLayout>
</layout>
我個(gè)人是比較喜歡DataBinding的,很多邏輯可以在xml解決,程序猿偷懶的好幫手啊。這里說明一下上面的 app:setCollapsedTitleTextColor 和 app:setExpandedTitleTextColor 這兩個(gè)屬性,其實(shí)這里DataBinding查找類里面對(duì)應(yīng)的公共方法,這兩個(gè)方法前者用于修改標(biāo)題收縮后的顏色,后者用于修改標(biāo)題展開后的顏色。
由于在toolbar里面設(shè)置的標(biāo)題,所以CollapsingToolbarLayout會(huì)自動(dòng)獲取toolbar的標(biāo)題來作為自己的標(biāo)題的。我們來看下效果吧:

總結(jié)
這里我只是簡(jiǎn)單介紹了CoordinatorLayout的一些常見的屬性和功能,如果大家希望了解更多的東西,可以去閱讀官方的文檔:官方文檔地址
這里說句題外話,CoordinatorLayout這個(gè)控件實(shí)現(xiàn)的效果很炫酷,它里面的NestedScrolling機(jī)制也讓人耳目一新,推薦大家都去仔細(xì)了解一下這個(gè)NestedScrolling機(jī)制,相信能從中受益不少。