一、概述
在某些App
當中,我們經常會見到類似於下面的這種設計:
Google
為這種設計提供了幾個類,讓我們只用實現很少的程式碼就能夠實現這種效果,避免了我們通過去監聽列表的滾動狀態來去改變頭部區域的顯示,這篇文章,我們就一步步來學習如何實現這種效果。
要實現上面這種效果,需要對下面幾方面的知識有所瞭解:
CoordinatorLayout
AppBarLayout
CollapsingToolbarLayout
- 實現了
NestedScrollingChild
介面的滾動佈局,例如RecyclerView
、NestedScrollView
等
這四者的關係可以用下面這張圖來表示:
其中CollapsingToolbarLayout
需要依賴於AppBarLayout
,因此我打算把文章的討論分為兩部分:
- 只採用
AppBarLayout
實現 - 採用
AppBarLayout + CollapsingToolbarLayout
實現
二、只採用AppBarLayout
實現
當只採用AppBarLayout
時,我們的佈局層次一般是下面這樣:
2.1 CoordinatorLayout
CoordinatorLayout
是一個重寫的ViewGroup
,它負責AppBarLayout
以及可滾動佈局之間的關係,因此,它是作為這兩者的直接父控制元件。
2.2 AppBarLayout
下的列表
一般是RecyclerView
或者ViewPager
,為了能讓它和AppBarLayout
協同工作,需要給列表控制元件新增下面這個屬性,這樣列表就會顯示在AppBarLayout
的下方:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
複製程式碼
2.3 AppBarLayout
的標誌位
需要將AppBarLayout
作為CoordinatorLayout
的直接子View
,同時它也是一個LinearLayout
,因此它的所有子View
都是線性排列的,而它對子View
的滾動顯示管理是通過子View
的app:layout_scrollFlags
屬性,注意,這個標誌位是在AppBarLayout
的子View
中宣告的,而不是AppBarLayout
中。
app:layout_scrollFlags
的值有下面五種可選:
scroll
exitUntilCollapsed
enterAlways
enterAlwaysCollapsed
snap
2.3.1 scroll
使得AppBarLayout
的子View
伴隨著滾動而收起或者展開,有兩點需要注意:
- 這個標誌位是其它四個標誌位生效的前提
- 帶有
scroll
標誌位的子View
必須放在AppBarLayout
中的最前面
現在我們給RecyclerView
設定了app:layout_behavior
,而AppBarLayout
中的兩個子View
都沒有設定scroll
標誌位:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
android:text="layout_scrollFlags=scroll"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
<TextView
android:gravity="center"
android:text="沒有設定layout_scrollFlags"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
複製程式碼
此時AppBarLayout
中的子View
都一直固定在CoordinatorLayout
的頂部:
AppBarLayout
中的第一個子View
加上app:layout_scrollFlags="scroll|exitUntilCollapsed"
標誌位:
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
android:text="layout_scrollFlags=scroll"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp"
app:layout_scrollFlags="scroll"/> //注意看這裡!
<TextView
android:gravity="center"
android:text="沒有設定layout_scrollFlags"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</android.support.design.widget.AppBarLayout>
複製程式碼
那麼此時滑動情況為:
- 設定了
scroll
的子View
可以在滾動後收起,而沒有設定的則不可以。 - 在手指向上移動的時候,優先收起
AppBarLayout
中的可收起View
,當它處於收起狀態時,下面的列表內容才開始向尾部滾動。 - 在手指往下移動的時候,優先讓下面的列表內容向頂部滾動,當列表滾動到頂端時,
AppBarLayout
的可收起View
才展開。
2.3.2 exitUntilCollapsed
對於可收起的子View
來說有兩種狀態:Enter
和Collapsed
。手指向上移動的時候,會優先收起AppBarLayout
中的子View
,而收起的最小高度為0
,假如希望它在收起時仍然保留部分可見,那麼就需要使用exitUntilCollapsed + minHeight
屬性,minHeight
就決定了收起時的最小高度是多少,也就是Collapsed
狀態。
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
android:text="layout_scrollFlags=scroll"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp"
android:minHeight="50dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"/>
<TextView
android:gravity="center"
android:text="沒有設定layout_scrollFlags"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</android.support.design.widget.AppBarLayout>
複製程式碼
2.3.3 enterAlways
exitUntilCollapsed
決定了手指向上移動時AppBarLayout
怎麼收起它的子View
,而enterAlways
則決定了手指向下移動時的行為。預設情況下,在手指向下移動時,會優先讓列表滾動到頂部,而如果設定了enterAlways
,那麼會優先讓AppBarLayout
中的子View
滾動到展開。
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 需要設定屬性 -->
<TextView
android:gravity="center"
android:text="layout_scrollFlags=scroll|enterAlways"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp"
app:layout_scrollFlags="scroll|enterAlways"/>
<TextView
android:gravity="center"
android:text="沒有設定layout_scrollFlags"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</android.support.design.widget.AppBarLayout>
複製程式碼
2.3.4 enterAlwaysCollapsed
enterAlwaysCollapsed
,enterAlways
以及minHeight
屬性一起配合使用,採用這個標誌位,主要是為了使得手指向下移動時出現下面這個效果:
- 優先滾動
AppBarLayout
中的子View
,但是並不是滾動到全部展開,而是隻滾動到minHeight
AppBarLayout
中的子View
滾動到minHeight
之後,開始滾動列表,直到列表滾動到頭部- 列表滾動到頭部之後,開始滾動
AppBarLayout
中的子View
,直到它完全展開
注意和exitUntilCollapsed
的minHeight
區分開來;
enterAlways|enterAlwaysCollapsed
的minHeight
,決定的是手指向下移動且列表沒有滾動到頂端的最大高度exitUntilCollapsed
的minHeight
,決定的是手指向上移動時的最小高度
這兩個標誌位和minHeight
是相互依賴的關係,如果宣告對應的標誌位,那麼minHeight
是沒有任何意義的;而如果只宣告瞭標誌位,沒有宣告minHeight
,那麼預設minHeight
為0
。
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 需要設定屬性 -->
<TextView
android:gravity="center"
android:text="layout_scrollFlags=scroll|enterAlways|enterAlwaysCollapsed, minHeight=50dp"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp"
android:minHeight="50dp"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"/>
<TextView
android:gravity="center"
android:text="沒有設定layout_scrollFlags"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</android.support.design.widget.AppBarLayout>
複製程式碼
2.3.5 snap
預設情況下,在手指從螢幕上抬起之後,AppBarLayout
中子View
的狀態就不會變化了,而如果我們設定了snap
標誌位,那麼在手指抬起之後,會根據子View
當前的偏移量,決定是讓它變為收起還是展開狀態。
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 需要設定屬性 -->
<TextView
android:gravity="center"
android:text="layout_scrollFlags=scroll|snap"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp"
app:layout_scrollFlags="scroll|snap"/>
<TextView
android:gravity="center"
android:text="沒有設定layout_scrollFlags"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</android.support.design.widget.AppBarLayout>
複製程式碼
2.4 監聽AppBarLayout
的滾動狀態
AppBarLayout
提供了監聽滾動狀態的介面,我們可以根據這個偏移值來改變介面的狀態:
private void setAppBar() {
final TextView moveView = (TextView) findViewById(R.id.iv_move_title);
AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.al_title);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
Log.d("AppBarLayout", "offset=" + verticalOffset);
int height = moveView.getHeight();
int minHeight = moveView.getMinHeight();
float fraction = verticalOffset / (float) (minHeight - height);
moveView.setAlpha(1 - fraction);
}
});
}
複製程式碼
這裡,我們根據offset
的值來改變alpha
,最終達到下面的效果:
verticalOffset
從0
變為負值:
2.6 AppBarLayout
小結
AppBarLayout
主要是掌握兩點:
app:layout_scrollFlags
幾種標誌位之前的區別- 如何監聽
AppBarLayout
的滾動
掌握完這兩點之後,我們就可以自由發揮,做出自己想要的效果了。
三、AppBarLayout
和CollapsingToolbarLayout
結合
CollapsingToolbarLayout
用於對Toolbar
進行包裝,因此,它是AppBarLayout
的直接子View
,同時也是Toolbar
的直接父View
,當使用CollapsingToolbarLayout
時我們的介面佈局一般是這樣的:
CollapsingToolbarLayout
的標誌比較多,而且需要和Toolbar
相結合,下面,我們就以一種比較常見的例子,來講解幾個比較重要的標誌位,先看效果:
AppBarLayout
的佈局如下:
<android.support.design.widget.AppBarLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/ctl_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="@color/colorPrimaryDark"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/iv_title"
android:src="@drawable/ic_bg"
android:layout_width="match_parent"
android:scaleType="centerCrop"
android:layout_height="150dp"
app:layout_collapseParallaxMultiplier="0.5"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:title="@string/app_name"
android:layout_width="match_parent"
android:layout_height="50dp"
app:title="Collapse"
app:navigationIcon="@android:drawable/ic_media_play"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
複製程式碼
下面,我們就來介紹上面這個佈局中比較重要的標誌位:
app:contentScrim
設定收起之後CollapsingToolbarLayout
的顏色,正如例子中的那樣,在收起之後,ImageView
的背景被我們設定的contentScrim
所覆蓋。app:layout_scrollFlags="scroll|exitUntilCollapsed"
scroll
標誌位是必須設定的,exitUntilCollapsed
保證了我們在手指上移的時候,CollapsingToolbarLayout
最多收起到Toolbar
的高度,使得它始終保持可見。app:layout_collapseMode="parallax"
和app:layout_collapseParallaxMultiplier="0.5"
layout_collapseMode
有兩種模式,parallax
表示視差效果,簡單地說,就是當CollapsingToolbarLayout
滑動的距離不等於背景滑動的距離,從而產生一種視差效果,而視差效果的大小由app:layout_collapseParallaxMultiplier
決定。app:layout_collapseMode="pin"
layout_collapseMode
的另一種模式,它使得Toolbar
一直固定在頂端。
除了上面這幾個重要的標誌位之外,相信大家還發現了CollapsingToolbar
在全部展開的時候會把標題設定為比較大,而當上移時,標題慢慢縮小為Toolbar
的標題大小,並移動到Toolbar
的標題所在位置。
四、總結
以上就是對於使用CoordinatorLayout
、AppBarLayout
、CollapsingToolbarLayout
實現標題跟隨列表滾動的簡要介紹,最主要的是掌握layout_scrollFlags
幾種標誌之間的區別。
五、參考文獻
Material Design之 AppbarLayout 開發實踐總結
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/