Material Design 控制元件知識梳理(2) AppBarLayout & CollapsingToolbarLayout

澤毛發表於2017-12-13

一、概述

在某些App當中,我們經常會見到類似於下面的這種設計:

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout
這種設計的目的是為了在初始時刻展示重要的資訊,但是當使用者需要檢視列表的時候,不至於被封面佔據過多的可視的區域,Google為這種設計提供了幾個類,讓我們只用實現很少的程式碼就能夠實現這種效果,避免了我們通過去監聽列表的滾動狀態來去改變頭部區域的顯示,這篇文章,我們就一步步來學習如何實現這種效果。 要實現上面這種效果,需要對下面幾方面的知識有所瞭解:

  • CoordinatorLayout
  • AppBarLayout
  • CollapsingToolbarLayout
  • 實現了NestedScrollingChild介面的滾動佈局,例如RecyclerViewNestedScrollView

這四者的關係可以用下面這張圖來表示:

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

其中CollapsingToolbarLayout需要依賴於AppBarLayout,因此我打算把文章的討論分為兩部分:

  • 只採用AppBarLayout實現
  • 採用AppBarLayout + CollapsingToolbarLayout實現

二、只採用AppBarLayout實現

當只採用AppBarLayout時,我們的佈局層次一般是下面這樣:

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

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的滾動顯示管理是通過子Viewapp: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的頂部:

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout
下面,我們給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才展開。
    Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

2.3.2 exitUntilCollapsed

對於可收起的子View來說有兩種狀態:EnterCollapsed手指向上移動的時候,會優先收起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>
複製程式碼

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

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>
複製程式碼

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

2.3.4 enterAlwaysCollapsed

enterAlwaysCollapsedenterAlways以及minHeight屬性一起配合使用,採用這個標誌位,主要是為了使得手指向下移動時出現下面這個效果:

  • 優先滾動AppBarLayout中的子View,但是並不是滾動到全部展開,而是隻滾動到minHeight
  • AppBarLayout中的子View滾動到minHeight之後,開始滾動列表,直到列表滾動到頭部
  • 列表滾動到頭部之後,開始滾動AppBarLayout中的子View,直到它完全展開

注意和exitUntilCollapsedminHeight區分開來;

  • enterAlways|enterAlwaysCollapsedminHeight,決定的是手指向下移動且列表沒有滾動到頂端最大高度
  • exitUntilCollapsedminHeight,決定的是手指向上移動時的最小高度

這兩個標誌位和minHeight是相互依賴的關係,如果宣告對應的標誌位,那麼minHeight是沒有任何意義的;而如果只宣告瞭標誌位,沒有宣告minHeight,那麼預設minHeight0

<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>
複製程式碼

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

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>
複製程式碼

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

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,最終達到下面的效果:

scroll_7.gif
在手指往上移動的過程當中,verticalOffset0變為負值:
Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout

2.6 AppBarLayout小結

AppBarLayout主要是掌握兩點:

  • app:layout_scrollFlags幾種標誌位之前的區別
  • 如何監聽AppBarLayout的滾動

掌握完這兩點之後,我們就可以自由發揮,做出自己想要的效果了。

三、AppBarLayoutCollapsingToolbarLayout結合

CollapsingToolbarLayout用於對Toolbar進行包裝,因此,它是AppBarLayout的直接子View,同時也是Toolbar的直接父View,當使用CollapsingToolbarLayout時我們的介面佈局一般是這樣的:

Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout
CollapsingToolbarLayout的標誌比較多,而且需要和Toolbar相結合,下面,我們就以一種比較常見的例子,來講解幾個比較重要的標誌位,先看效果:
Material Design 控制元件知識梳理(2)   AppBarLayout & CollapsingToolbarLayout
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的標題所在位置。

四、總結

以上就是對於使用CoordinatorLayoutAppBarLayoutCollapsingToolbarLayout實現標題跟隨列表滾動的簡要介紹,最主要的是掌握layout_scrollFlags幾種標誌之間的區別。

五、參考文獻

Material Design之 AppbarLayout 開發實踐總結


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章