1. 程式人生 > >看,這個工具欄能伸縮摺疊——Android CollapsingToolbarLayout使用介紹

看,這個工具欄能伸縮摺疊——Android CollapsingToolbarLayout使用介紹

我非常喜歡Material Design裡摺疊工具欄的效果,bilibili Android客戶端視訊詳情頁就是採用的這種設計。這篇文章的第二部分我們就通過簡單的模仿bilibili視訊詳情頁的實現來了解下CollapsingToolbarLayout的使用。文章的第三部分介紹了CollapsingToolbarLayout與TabLayout的組合使用。

有基礎的朋友可以直接跳過第一部分。

一、相關基礎屬性介紹

Android studio中有一個Activity模板叫ScrollingActivity,它實現的就是簡單的可摺疊工具欄,我們將此模板新增到專案中。


ScrollingActivity.gif

ScrollingActivity的佈局程式碼如下

<?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">
<android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" 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" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <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" android:src="@android:drawable/ic_dialog_email" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|end" /> </android.support.design.widget.CoordinatorLayout>

AppBarLayout是一種支援響應滾動手勢的app bar佈局(比如工具欄滾出或滾入螢幕),CollapsingToolbarLayout則是專門用來實現子佈局內不同元素響應滾動細節的佈局。

與AppBarLayout組合的滾動佈局(Recyclerview、NestedScrollView等)需要設定app:layout_behavior=”@string/appbar_scrolling_view_behavior”(上面程式碼中NestedScrollView控制元件所設定的)。沒有設定的話,AppBarLayout將不會響應滾動佈局的滾動事件。

CollapsingToolbarLayout和ScrollView一起使用會有滑動bug,注意要使用NestedScrollView來替代ScrollView。

AppBarLayout的子佈局有5種滾動標識(就是上面程式碼CollapsingToolbarLayout中配置的app:layout_scrollFlags屬性):

  1. scroll:將此佈局和滾動時間關聯。這個標識要設定在其他標識之前,沒有這個標識則佈局不會滾動且其他標識設定無效。
  2. enterAlways:任何向下滾動操作都會使此佈局可見。這個標識通常被稱為“快速返回”模式。
  3. enterAlwaysCollapsed:假設你定義了一個最小高度(minHeight)同時enterAlways也定義了,那麼view將在到達這個最小高度的時候開始顯示,並且從這個時候開始慢慢展開,當滾動到頂部的時候展開完。
  4. exitUntilCollapsed:當你定義了一個minHeight,此佈局將在滾動到達這個最小高度的時候摺疊。
  5. snap:當一個滾動事件結束,如果檢視是部分可見的,那麼它將被滾動到收縮或展開。例如,如果檢視只有底部25%顯示,它將摺疊。相反,如果它的底部75%可見,那麼它將完全展開。

CollapsingToolbarLayout可以通過app:contentScrim設定摺疊時工具欄佈局的顏色,通過app:statusBarScrim設定摺疊時狀態列的顏色。預設contentScrim是colorPrimary的色值,statusBarScrim是colorPrimaryDark的色值。這個後面會用到。

CollapsingToolbarLayout的子佈局有3種摺疊模式(Toolbar中設定的app:layout_collapseMode)

  1. off:這個是預設屬性,佈局將正常顯示,沒有摺疊的行為。
  2. pin:CollapsingToolbarLayout摺疊後,此佈局將固定在頂部。
  3. parallax:CollapsingToolbarLayout摺疊時,此佈局也會有視差摺疊效果。

當CollapsingToolbarLayout的子佈局設定了parallax模式時,我們還可以通過app:layout_collapseParallaxMultiplier設定視差滾動因子,值為:0~1。

FloatingActionButton這個控制元件通過app:layout_anchor這個設定錨定在了AppBarLayout下方。FloatingActionButton原始碼中有一個Behavior方法,當AppBarLayout收縮時,FloatingActionButton就會跟著做出相應變化。關於CoordinatorLayout和Behavior,我下一篇文章會和大家一起學習。

這一堆屬性看著有點煩,大家可以新建一個ScrollingActivity模板去實驗一下玩玩。

二、模仿bilibili客戶端視訊詳情頁

我們先對原介面分析一下。

嗶哩嗶哩Android客戶端視訊詳情頁.gif

介面初始,CollapsingToolbarLayout是展開狀態,顯示的是視訊封面。我們向上滾動介面,CollapsingToolbarLayout收縮。當AppBarLayout完全摺疊的時候視訊av號隱藏,顯示出來一個小電檢視標和“立即播放”,點選則使AppBarLayout完全展開,CollapsingToolbarLayout子佈局由ImageView切換為視訊彈幕播放器。

額…彈幕播放器…

B站很早就開源了一個彈幕引擎,還起了個狂拽酷炫吊炸天的名字叫“烈焰彈幕使 ”(一看就是二次元程式猿們的作品→_→),原始碼在github上,專案名叫DanmakuFlameMaster

來我們先看修改完成的佈局。

<?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:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.design.widget.AppBarLayout
    android:id="@+id/app_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/app_bar_height"
    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:contentScrim="?attr/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:src="@drawable/diqiu"
            app:layout_collapseMode="parallax"
            app:layout_collapseParallaxMultiplier="0.7"
            android:fitsSystemWindows="true"/>

        <!--視訊及彈幕控制元件-->
        <FrameLayout
            android:id="@+id/video_danmu"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_collapseMode="parallax"
            app:layout_collapseParallaxMultiplier="0.7"
            android:fitsSystemWindows="true"
            android:visibility="gone">
            <VideoView
                android:id="@+id/videoview"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <!--嗶哩嗶哩開源的彈幕控制元件-->
            <master.flame.danmaku.ui.widget.DanmakuView
                android:id="@+id/danmaku"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </FrameLayout>

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_collapseMode="pin"
            app:popupTheme="@style/AppTheme.PopupOverlay" >

            <!--自定義帶圖片的立即播放按鈕-->
            <android.support.v7.widget.ButtonBarLayout
                android:id="@+id/playButton"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:visibility="gone">
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_gravity="center_horizontal"
                    android:src="@mipmap/ic_play_circle_filled_white_48dp"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#ffffff"
                    android:text="立即播放"
                    android:layout_gravity="center_vertical"
                   />
            </android.support.v7.widget.ButtonBarLayout>

        </android.support.v7.widget.Toolbar>
    </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_scrolling" />

<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"
    android:src="@mipmap/ic_play_circle_filled_white_48dp"
    app:layout_anchor="@id/app_bar"
    app:layout_anchorGravity="bottom|end" />

</android.support.design.widget.CoordinatorLayout>

我把colorPrimary的色值修改成了B站的“少女粉”,播放的圖示是從網上找的。

<color name="colorPrimary">#FA7199</color>

因為我們要實現沉浸式狀態列,所以就需要先把整個activity設定成狀態列透明模式。然後在佈局檔案中,把CollapsingToolbarLayout裡要實現沉浸式的控制元件設定上android:fitsSystemWindows=”true”,如果沒有設定,則子佈局會位於狀態列下方,未延伸至狀態列。

佈局並不算複雜,接下來先實現無彈幕播放時的功能,。

我們需要監聽CollapsingToolbarLayout的摺疊、展開狀態。唉我去,官方並沒有提供現成的方法(⊙_⊙?)。

檢視原始碼,可以看到CollapsingToolbarLayout是通過實現AppBarLayout的OnOffsetChangedListener介面,根據AppBarLayout的偏移來實現子佈局和title的視差移動以及ContentScrim和StatusBarScrim的顯示。那麼我們也可以通過呼叫AppBarLayout的addOnOffsetChangedListener方法監聽AppBarLayout的位移,判斷CollapsingToolbarLayout的狀態。

先寫一個列舉定義出CollapsingToolbarLayout展開、摺疊、中間,這三種狀態。

 private CollapsingToolbarLayoutState state;

 private enum CollapsingToolbarLayoutState {
    EXPANDED,
    COLLAPSED,
    INTERNEDIATE
}

接下來對AppBarLayout進行監聽,判斷CollapsingToolbarLayout的狀態並實現相應的邏輯。

為了讓大家對狀態看著更直觀,我在修改狀態值的時候把title一起進行了修改。

使用CollapsingToolbarLayout的時候要注意,在完成CollapsingToolbarLayout設定之後再呼叫Toolbar的setTitle()等方法將沒有效果,我們需要改為呼叫CollapsingToolbarLayout的setTitle()等方法來對工具欄進行修改。(具體原因各位親去看下CollapsingToolbarLayout原始碼就知道了 ( ˙-˙ ) )

    AppBarLayout  app_bar=(AppBarLayout)findViewById(R.id.app_bar);
    app_bar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {

            if (verticalOffset == 0) {
                if (state != CollapsingToolbarLayoutState.EXPANDED) {
                    state = CollapsingToolbarLayoutState.EXPANDED;//修改狀態標記為展開
                    collapsingToolbarLayout.setTitle("EXPANDED");//設定title為EXPANDED
                }
            } else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
                if (state != CollapsingToolbarLayoutState.COLLAPSED) {
                    collapsingToolbarLayout.setTitle("");//設定title不顯示
                    playButton.setVisibility(View.VISIBLE);//隱藏播放按鈕
                    state = CollapsingToolbarLayoutState.COLLAPSED;//修改狀態標記為摺疊
                }
            } else {
                if (state != CollapsingToolbarLayoutState.INTERNEDIATE) {
                    if(state == CollapsingToolbarLayoutState.COLLAPSED){
                        playButton.setVisibility(View.GONE);//由摺疊變為中間狀態時隱藏播放按鈕
                    }
                    collapsingToolbarLayout.setTitle("INTERNEDIATE");//設定title為INTERNEDIATE
                    state = CollapsingToolbarLayoutState.INTERNEDIATE;//修改狀態標記為中間
                }
            }
        }
    });

然後對播放按鈕設定監聽,點選則呼叫AppBarLayout的setExpanded(true)方法使工具欄展開。

CollapsingToolbarLayout狀態監聽演示.gif

嗶哩嗶哩客戶端的title是固定不動的,可以呼叫CollapsingToolbarLayout的setTitleEnabled(false)方法實現。

視訊播放時,呼叫 NestedScrollView的setNestedScrollingEnabled(false)方法可以使AppBarLayout不響應滾動事件。

細心的朋友可能發現了嗶哩嗶哩客戶端為了避免視訊封面圖片顏色過淺影響狀態列資訊的顯示,加了一個漸變的不透明層。

實現漸變遮罩層很簡單。先在res/drawable資料夾下新建了一個名為gradient的xml檔案,其中程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient
        android:startColor="#33000000"
        android:endColor="#00000000"
        android:angle="270" />

</shape>

shape節點中,可以通過android:shape來設定形狀,預設是矩形。gradient節點中angle的值270是從上到下,0是從左到右,90是從下到上。起始顏色#33000000是20%不透明度的黑色,#00000000表示全透明。

然後在CollapsingToolbarLayout裡的ImageView程式碼下面加上一個自定義view,背景設定為上面的漸變效果。

<View
   android:layout_width="match_parent"
   android:layout_height="40dp"
   android:background="@drawable/gradient"
   android:fitsSystemWindows="true"
/>    

一般狀態列的高度大概在20dp左右,我為了讓漸變效果比較自然,並且不過多影響圖(mei)片(zi),把高度設定成了40dp。(狀態列能看清了,妹子臉也沒黑,挺好 (๑• . •๑) )

有無漸變遮罩層的對比.jpg

我省略了彈幕播放的相關實現,接下來只要在播放按鈕監聽中寫出封面圖片的隱藏、視訊和彈幕彈幕控制元件的顯示初始化及播放邏輯,在AppBarLayout的三種狀態監聽中根據是否視訊在播放寫出其他相應邏輯就好了,感興趣的朋友可以下載嗶哩嗶哩的“烈焰彈幕使”原始碼DanmakuFlameMaster玩玩。

模仿嗶哩嗶哩視訊詳情頁.gif

真的不是我懶得上程式碼了,真的…(基友:趕緊的,開黑了。 我:等等我,馬上來!\(≧▽≦)/)

三.CollapsingToolbarLayout與TabLayout

CollapsingToolbarLayout與TabLayout組合使用的效果也不錯。


CollapsingToolbarLayout與TabLayout.gif

來看下CollapsingToolbarLayout裡的程式碼

<?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">

<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>

TabLayout沒有設定app:layout_collapseMode,在CollapsingToolbarLayout收縮時就不會消失。

CollapsingToolbarLayout收縮時的高度是Toolbar的高度,所以我們需要把Toolbar的高度增加,給TabLayout留出位置,這樣收縮後TabLayout就不會和Toolbar重疊。

Toolbar的高度增加,title會相應下移。android:gravity=”top”方法使Toolbar的title位於Toolbar的上方,然後通過app:titleMarginTop調整下title距頂部高度,這樣Toolbar就和原來顯示的一樣了。

CollapsingToolbarLayout還可以和Palette搭配使用,但是我感覺在實際使用中有些坑,因為CollapsingToolbarLayout中的圖片不確定,Palette從圖片中獲取到的色彩很可能不是你想要的。

感興趣的朋友可以自己查下Palette的用法。

就是這些。 []~( ̄▽ ̄)~*