AppbarLayout的简单用法

在许多App中看到, toolbar有收缩和扩展的效果, 例如:

appbar.gif

要实现这样的效果, 需要用到:
CoordinatorLayoutAppbarLayout的配合, 以及实现了NestedScrollView的布局或控件.
AppbarLayout是一种支持响应滚动手势的app bar布局, CollapsingToolbarLayout则是专门用来实现子布局内不同元素响应滚动细节的布局.

与AppbarLayout组合的滚动布局(RecyclerView, NestedScrollView等),需要设置 app:layout_behavior = "@string/appbar_scrolling_view_behavior" .没有设置的话, AppbarLayout将不会响应滚动布局的滚动事件.

我们回到再前面一章"Toolbar的使用", 将布局改动如下:

 <?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"
    tools:context="com.truly.mytoolbar.MainActivity">
    <android.support.design.widget.AppBarLayout
        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="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
           android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_scrollFlags="scroll"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:title="Title" />
    </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:id="@+id/tv_content"
            android:layout_margin="16dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:lineSpacingMultiplier="2"
            android:text="@string/textContent" />
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

先看下效果再来解释为什么.


appbar_2.gif

可以看到,

  • 随着文本往上滚动, 顶部的toolbar也往上滚动, 直到消失.
  • 随着文本往下滚动, 一直滚到文本的第一行露出来, toolbar也逐渐露出来

解释:
从上面的布局中可以看到, 其实在整个父布局CoordinatorLayout下面, 是有2个子布局

  • AppbarLayout
  • NestedScrollView
    NestedScrollView先放一放, 我们来看AppbarLayout.

AppBarLayout 继承自LinearLayout,布局方向为垂直方向。所以你可以把它当成垂直布局的LinearLayout来使用。AppBarLayout是在LinearLayou上加了一些材料设计的概念,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。

注意:

上面提到的"某个可滚动View", 可以理解为某个ScrollView. 就是说,当某个ScrollView发生滚动时,你可以定制你的“顶部栏”应该执行哪些动作(如跟着一起滚动、保持不动等等)。

这里某个ScrollView就是NestedScrollView或者实现了NestedScrollView机制的其它控件, 如RecyclerView. 它有一个布局行为Layout_Behavior:

app:layout_behavior="@string/appbar_scrolling_view_behavior"

这是一个系统behavior, 从字面意思就可以看到, 是为appbar设置滚动动作的一个behavior. 没有这个属性的话, Appbar就是死的, 有了它就有了灵魂.

我们可以通过给Appbar下的子View添加app:layout_scrollFlags来设置各子View执行的动作. scrollFlags可以设置的动作如下:

(1) scroll: 值设为scroll的View会跟随滚动事件一起发生移动。就是当指定的ScrollView发生滚动时,该View也跟随一起滚动,就好像这个View也是属于这个ScrollView一样。

上面这个效果就是设置了scroll之后的.

(2) enterAlways: 值设为enterAlways的View,当任何时候ScrollView往下滚动时,该View会直接往下滚动。而不用考虑ScrollView是否在滚动到最顶部还是哪里.

我们把layout_scrollFlags改动如下:

app:layout_scrollFlags="scroll|enterAlways"

效果如下:


appbar_3.gif

(3) exitUntilCollapsed:值设为exitUntilCollapsed的View,当这个View要往上逐渐“消逝”时,会一直往上滑动,直到剩下的的高度达到它的最小高度后,再响应ScrollView的内部滑动事件。

怎么理解呢?简单解释:在ScrollView往上滑动时,首先是View把滑动事件“夺走”,由View去执行滑动,直到滑动最小高度后,把这个滑动事件“还”回去,让ScrollView内部去上滑。

把属性改下再看效果

<android.support.v7.widget.Toolbar
    ...
    android:layout_height="?attr/actionBarSize"
    android:minHeight="20dp"
    app:layout_scrollFlags="scroll|exitUntilCollapsed"
/>
appbar_4.gif

(4) enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是enterAlways效果,当View的高度达到最小高度时,View就暂时不去往下滚动,直到ScrollView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束

这个得把高度加大点才好实验. 来看:

<android.support.v7.widget.Toolbar
    ...
    android:layout_height="200dp"
    android:minHeight="?attr/actionBarSize"
    app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
</android.support.design.widget.AppBarLayout>
appbar_5.gif

Attention:

其实toolbar的默认最小高度minHeight就是"?attr/actionBarSize" , 很多时候可以不用设置. 而且从图上可以看出, 其实这里有个缺陷, 就是title的位置和toolbar上的图标行脱离了, 即使在布局里添加了 android:gravity="bottom|start", 在toolbar滚动的时候, title还在, 图标滚动到隐藏了.


image.png

后面讲解的CollapsingToolbarLayout可以解决这个问题, 这里先丢出来.

(5) snap:简单理解,就是Child View滚动比例的一个吸附效果。也就是说,Child View不会存在局部显示的情况,滚动Child View的部分高度,当我们松开手指时,Child View要么向上全部滚出屏幕,要么向下全部滚进屏幕,有点类似ViewPager的左右滑动

appbar_6.gif

引入CollapsingToolbarLayout

CollapsingToolbarLayout是用来对Toolbar进行再次包装的ViewGroup,主要是用于实现折叠(其实就是看起来像伸缩~)的App Bar效果。它需要放在AppBarLayout布局里面,并且作为AppBarLayout的直接子View。CollapsingToolbarLayout主要包括几个功能(参照了官方网站上内容,略加自己的理解进行解释):

(1) 折叠Title(Collapsing title):当布局内容全部显示出来时,title是最大的,但是随着View逐步移出屏幕顶部,title变得越来越小。你可以通过调用setTitle方法来设置title。

(2)内容纱布(Content scrim):根据滚动的位置是否到达一个阀值,来决定是否对View“盖上纱布”。可以通过setContentScrim(Drawable)来设置纱布的图片. 默认contentScrim是colorPrimary的色值

(3)状态栏纱布(Status bar scrim):根据滚动位置是否到达一个阀值决定是否对状态栏“盖上纱布”,你可以通过setStatusBarScrim(Drawable)来设置纱布图片,但是只能在LOLLIPOP设备上面有作用。默认statusBarScrim是colorPrimaryDark的色值.

(4)视差滚动子View(Parallax scrolling children): 子View可以选择在当前的布局当时是否以“视差”的方式来跟随滚动。(PS:其实就是让这个View的滚动的速度比其他正常滚动的View速度稍微慢一点)。将布局参数app:layout_collapseMode设为parallax

(5)将子View位置固定(Pinned position children):子View可以选择是否在全局空间上固定位置,这对于Toolbar来说非常有用,因为当布局在移动时,可以将Toolbar固定位置而不受移动的影响。 将app:layout_collapseMode设为pin。

我们来更改一下布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 
    ...>

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="150dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_collapseMode="parallax"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:title="Title" />
        </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:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="16dp"
            android:lineSpacingMultiplier="2"
            android:text="@string/textContent" />
    </android.support.v4.widget.NestedScrollView>

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

可以看到, 我们把原本属于toolbar的几个属性移到了CollapsingToolbarLayout上. 分别是:

android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|exitUntilCollapsed"

同时给toolbar增加了一个折叠模式属性

app:layout_collapseMode="parallax"

我们来看下效果:


appbar_8.gif

嗯嗯, 折叠模式不对, toolbar的顶部图标没了. 我们改下折叠模式:

app:layout_collapseMode="pin"

再看效果:


appbar_9.gif

我们把scrollFlags属性改下, 看下对比:

app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
appbar_10.gif

效果还是蛮不错的, 有了点Google Material Design的感觉了.

上面说CollapsingToolbarLayout是个ViewGroup, 那么肯定还可以添加控件. 那么我们在里面添加一个ImageView来看看. 更改布局如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 
    ...>
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">
        <android.support.design.widget.CollapsingToolbarLayout
            ...
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/darkbg"
                app:layout_collapseMode="parallax" />
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:title="Title" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        ...
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <TextView
            ... />
    </android.support.v4.widget.NestedScrollView>

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

来看下效果:


appbar_11.gif

嗯, 有了点意思, 但不美观, 上部的toolbar和图片不协调. toolbar应该有默认的背景属性, 我们去掉它看看.

 <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/ThemeOverlay.AppCompat.Light"
    app:title="Title" />

再看下效果:

appbar_12.gif

这次真的不错哦, 已经和很多大公司的app相像了. 但是为什么去掉toolbar的background就可以得到透明背景呢? 说句实话, 没找到原因.

不过我们没有给CollapsingToolbarLayout设置contentScrim属性哦, 给它加个属性看看.

<android.support.design.widget.CollapsingToolbarLayout
    ...
    app:contentScrim="?attr/colorPrimary"
    ...>
appbar_13.gif

嗯嗯, 好像还不如没设置这个属性好呢.

什么时候需要contentScrim属性呢?
因为这个布局里面给CollapsingToolbarLayout的layout_scrollFlags设置的是 "scroll|enterAlways|enterAlwaysCollapsed" , toolbar会全部消失的, 所以感觉不是很美观. 如果将layout_scrollFlags属性改为 "scroll|exitUntilCollapsed" , 效果会好点, 适合toolbar还是需要展示的场合.

appbar_14.gif

不管怎么样, 先去掉contentScrim属性吧.

目前有很多APP比较喜欢采用沉浸式设计, 简单点说就是将状态栏和导航栏都设置成透明或半透明的.

我们来把状态栏statusBar设置成透明. 在style主题中的AppTheme里增加一条:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    ...

    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

在布局里面, 将ImageView和所有它上面的父View都添加fitsSystemWindows属性.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 
    ...
    android:fitsSystemWindows="true">
    <android.support.design.widget.AppBarLayout
        ...
        android:fitsSystemWindows="true">
        <android.support.design.widget.CollapsingToolbarLayout
            ...
            android:fitsSystemWindows="true">
            <ImageView
                ...
                android:fitsSystemWindows="true" />
            <android.support.v7.widget.Toolbar
                ... />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>


    <android.support.v4.widget.NestedScrollView
        ...>
        <TextView
            ... />
    </android.support.v4.widget.NestedScrollView>

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

最后来看下效果:


appbar_15.gif

其实还可以在CollapsingToolbarLayout里设置statusBarScrim为透明色, 不过有点问题, 最顶部的toolbar没有完全隐藏, 还留了一点尾巴.


image.png

难道就这个属性就没用吗? 我们把layout_scrollFlags改成 "scroll|exitUntilCollapsed" 看看:


image.png

这个时候toolbar不用隐藏, 所以还是美美的.

AppbarLayout整个做成沉浸式之后, 状态栏的图标可能会受到封面图片颜色过浅的影响, 可以给其加一个渐变的不透明层.

渐变遮罩设置方法:

在res/drawable文件夹下新建一个名为status_gradient的xml资源文件, 代码如下:

 <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:angle="270"
        android:endColor="@android:color/transparent"
        android:startColor="#CC000000" />
        <!-- shape节点中, 可以通过android:shape来设置形状, 默认是矩形.
        gradient节点中angle的值270是从上到下,0是从左到右,90是从下到上。 
        此处的效果就是从下向上, 颜色逐渐由纯透明慢慢变成黑透色-->
</shape>

布局中, 在ImageView下面增加一个View, 背景设为上面的渐变遮罩.

<!-- 在顶部增加一个渐变遮罩, 防止出现status bar 状态栏看不清 -->
<View
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@drawable/status_gradient"
    app:layout_collapseMode="pin"
    android:fitsSystemWindows="true" />

给遮罩设置折叠模式: app:layout_collapseMode="pin" , 折叠到顶部后定住. 来看下效果.

image.png
image.png

上图是展开状态的对比, 后面的是没有添加遮罩的效果, 前面是添加了遮罩的效果. 下图是添加了遮罩折叠后的效果. 有点黑暗系影片的感觉哦.

FloatingActionButton再次表演

作为Google Material Design的一个重要控件, FloatingActionButton怎么可能不在AppbarLayout中起点作用呢. 我们在布局中加一个悬浮按钮, 让它的锚点挂载Appbar的右下角. 这样这个悬浮按钮就和Appbar关联起来了.

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

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

    <android.support.v4.widget.NestedScrollView
    ...
    </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="16dp"
        android:src="@drawable/ic_share_white_24dp"
        android:elevation="4dp"
        app:pressedTranslationZ="16dp"
        app:rippleColor="@android:color/white"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|end"/>

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

我们来看下效果.

appbar_16.gif

好吧, 美美的Toolbar完成了, 有点Google Material Design扑面而来的感觉了.

这篇文章已经很长了, 还有些内容就不放进来了, 后面陆续完善.

借鉴了很多资料, 写的时候忘了记录下来, 如对您有损, 请联系我进行删除或更改. 致歉!