细说 AppbarLayout,如何理解可折叠 Toolbar 的定制

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/briblue/article/details/77075198

Material Design 是个好东西,它的出现使得 Android 也能定制高颜值的界面,并且指导了如果实现复杂炫丽的交互效果,而 Android Surpport Desgin 这个支持包就是 Android 官方对 Material Design 的代码实现。

Android Support Desgin 这个包中提供了一系列的组件如:CoordinatorLayout、AppBarLayout、FloatingActionButton 等等。其中 CoordinatorLayout 是核心,它是包内其它组件能够正常工作的前提。但是,本文的主题不是 CoordinatorLayout,主题是 AppBarLayout。

本文的目的就是详细地介绍怎么样通过 AppBarLayout 与 CoordinatorLayout 的配合使用,去定制一个可折叠的 Toolbar。然后继续通过 CollapsingToolbarLayout 进一步增强 Toolbar 的视觉效果。如果有人对可折叠的的 Toolbar 还不了解,那么请看下面的示例。
这里写图片描述

本文涉及的内容主要有 AppBarLayout 与 CollapsingToolbarLayout 的属性与 API 的细节。

内容较多,下面,开始一一道来。

CoordinatorLayout、AppBarLayout、Toolbar 之间的关系

有同学可能不是太了解 CoordinatorLayout 这个类,其实没有太大的关系,下面我会简单介绍一下它的大致功能。

在 Android 为实现 Material Design 提供的支持包 android support design 中,CoordinatorLayout 毫无疑问是最核心的,它通过子 View 对象配置的 Behavior,实现了子 View 与 CoordinatorLayout、子 View 与子 View 之间一系列复杂的交互。

所以,CoordinatorLayout 编程的关键是它的子 View 们配置的 Behavior.

AppBarLayout 本身有默认的 Behavior,这使得它能够响应依赖对象的位置变化或者是 CoordinatorLayout 中产生的嵌套滑动事件,这从它的源码中可以看出来。

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
}

AppBarLayout 对象默认配置了一个 Behavior。而正是这个 Behavior,它会响应外部的嵌套滑动事件,然后根据特定的规则去伸缩和滑动内部的子 View。本文的主要目的就是要讲解这些特定的规则及它们作用后的效果。

AppBarLayout 本身想提供一个 AppBar 的概念,所以严格地讲它本身与 Toolbar 没有直接的关系。AppBarLayout 内部的子 View 不一定非要是 Toolbar,它可以是任何 View,比如,你可以放置进去一张图片、一个列表、一个 ViewPager 等等。
我们知道,Android 的历史进程中,大概有 TitleBar、ActionBar、Toolbar 的进化,这是 Android 设计语言的改良过程。而后来随着 Material Design 设计的出现,它又提供了 AppBar 的概念,而 AppBarLayout 则是 AppBar 在 Android 中的代码实现。
这里写图片描述

AppBarLayout 虽然和 Toolbar 没有直接联系,但是当 Toolbar 内置在 AppbarLayout 中的时候,Toolbar 的效果增强了,这使得开发者非常愿意用 AppBarLayout 与 Toolbar 配合使用,这比单独使用 Toolbar 炫丽的多。所以,基本上有 AppBarLayout 的地方就有 Toolbar。通过 AppBarLayout 实现一个可伸缩折叠的 Toolbar 也是本文的目的。

当我们运用 support design 中的组件时,我们应该拥有下面几个最基本的意识:

1. CoordinatorLayout 是这个库的组织容器,一切基于 support design 扩展出来的特性都应该发生在 CoordinatorLayout 及它的子 View 体系中。

2. AppbarLayout 应该作为一个 CoordinatorLayout 的直接子 View,否则它与普通的 LinearLayout 无异。

3. AppbarLayout 的子 View 不仅仅是 Toolbar,它们可以是任何的 View,但通常和 Toolbar 配合使用。

AppBarLayout 基本使用方法

AppBarLayout 是 android support design 这个支持包中的类,前面说过它的一切效果都建立在 CoordinatorLayout 这个父类容器之上,AppBarLayout 要想正常发挥它的所有特性,那么它必须作为 CoordinatorLayout 的直接子类。

引入依赖

android support design 没有内置在 SDK 中,所以我们需要引入依赖。

compile 'com.android.support:design:25.0.1'

与嵌套滑动组件配合

在 AppBarLayout 官方文档注释中有这么一段。

AppBarLayout 需要和一个独立的兄弟 View 配合使用,这个兄弟 View 是一个嵌套滑动组件,只有这样 AppBarLayout 才能知道什么时候开始滑动。它们之间关系的绑定通过给嵌套滑动的组件设立特定的 Behavior,那就是 AppBarLayout.ScrollingViewBehavior。

然后,官方还给出了示例。

<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.v4.widget.NestedScrollView
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             app:layout_behavior="@string/appbar_scrolling_view_behavior">

         <!-- Your scrolling content -->

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

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

         <android.support.v7.widget.Toolbar
                 ...
                 app:layout_scrollFlags="scroll|enterAlways"/>

         <android.support.design.widget.TabLayout
                 ...
                 app:layout_scrollFlags="scroll|enterAlways"/>

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

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

上面布局文件中,NestedScrollView 就是那个配套的滑动组件,它需要和 AppBarLayout 进行绑定,所以它必须指定 Behavior。在 xml 中通过

app:layout_behavior="@string/appbar_scrolling_view_behavior"

多说两句,有同学可能会想一定要是 NestedScrollView 吗?

当然不是,在 CoordinatorLayout 中嵌套滑动的本质是一个 NestedScrollingChild 对象。

NestedScrollingChild 是一个接口,目前它的实现类有 4 个。
这里写图片描述

所以除了使用 NestedScrollView,我们还经常使用 RecyclerView 和 SwipeRefreshLayout 作为配套的嵌套滑动组件,这是其它博文都没有提到的,希望大家注意。

可能大家注意到了上面示例中有 app:layout_scrollFlags 这样的属性,大家一定很好奇,它们是如何作用的。不要着急,下面就讲这一块的内容。

layout_scrollFlags 滑动效果的配置

AppBarLayout 本身也是一个垂直方向的 LinearLayout,所以它的滑动主要是针对内部子 View 的滑动。这就需要设立一种规则来定义滑动的行为。

这个规则就是 AppBarLayout 中内部的子 View 需要在 xml 中配置 layout_scrollFlags。
layout_scrollFlags 取值有 5 个。

  • scroll
  • enterAlways
  • enterAlwaysCollapsed
  • exitUntilCollapsed
  • snap

说实话,这 5 个值并不复杂,但是因为命名的关系,它们有点难以理解,很多人应该就是在这一位置卡壳了很久。因为其它的博文都是直接讲 5 个值的用法,然后再配置上动图来解释说明。而对于我而言,这种方式有值得改进的地方,我更愿意先解释目的再解释手段。

我们提炼几个关键字。

它们分别是滑动、折叠、进入、离开、snap。

滑动 scroll

滑动是基础,后面的几个属性都是建立在此属性上的。滑动大家应该很好理解,只有在 AppBarLayout 中的子 View 配置了 scroll 属性,这个 AppBarLayout 都会响应。

<?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"
    tools:context="com.frank.supportdesigndemo.appbarlayout.AppbarLayoutActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll" />
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/large_text"></TextView>
    </android.support.v4.widget.NestedScrollView>

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

我们可以看看动画
这里写图片描述

scroll 这个 layout_scrollFlags 使得 Toolbar 像是 NestedScrollView 本身的一部分一样,它们同步响应滑动。

向上滑动的时候,Toolbar 先滑动,然后 NestedScrollView 中的内容再滑动。

向下滑动的时候,NestedScrollView 中的内容先滑动,然后 Toolbar 再一起滑动。

enter 和 exit 的概念

这两个直译为中文就是进入和离开。这里的主语是 AppBarLayout 中的内容,宾语是 CoordinatorLayout。
这里写图片描述
当 AppBarLayout 中的内容要从 CoordinatorLayout 外面进入内部时,我们用 enter 指代这种行为,对应的手势就是向下滑动。

当 AppBarLayout 中的内容从 CoordinatorLayout 内部向外部方向移动时,我们用 exit 指代这种行为,对应的手势是向上滑动。

collapsed 折叠的概念

其实我更愿意用伸缩解释这个概念。
这里写图片描述

红框部分标明不能再被压缩的范围,其它部分都可以在滑动的过程中伸缩。

collapsed 的高度由相应的 View 的 minHeight 属性指定,也就是一个 View 的最小高度。

与 collapsed 相关的 layout_scrollFlags 有两个:exitUntilCollapsed 和 enterAlwaysCollapsed。

下面一一讲解。

exitUntilCollapsed

前面讲过

当 AppBarLayout 中的内容从 CoordinatorLayout 内部向外部方向移动时,我们用 exit 指代这种行为,对应的手势是向上滑动。

exit 指得是向上滑动的手势动作,但是 exitUntilCollapsed 代表什么呢?
我们再看这张图。
这里写图片描述

黄色部分是可以被压缩的,而红色部分是不能再被压缩的地方。

exitUntilCollapsed 表示的是,向上滑动时,AppBarLayout 中设置了该属性的 View 可以滑动,等到这个 View 可视范围被压缩到 collapsed 指定的高度也就是 minHeight 时,这个 View 不再滑动。

如果还没有理解,那就结合下面的实例效果。

<android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:minHeight="100dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed" />
    </android.support.design.widget.AppBarLayout>

运行效果如下:
这里写图片描述

仔细想一想,其实 scroll 和 exitUntilCollapsed 也没有什么太大的区别。

区别在于 exitUntilCollapsed 的存在,让 scroll 滑动受到了一定的限制。这个限制就是 scoll 不再能够进行完全的滑动,因为 collapsed 距离的存在。

enterAlways

enterAlways 也需要和 scroll 配合使用。

我们回到前面仔细观察单独 scroll 标志起作用的动图。

可以发现,这个 scorll 标志起作用的时候,enter 行为发生时,是 NestedScrollView 内容先滑动,然后才是 Toolbar 开始滑动。

而 enterAlways 这个标志与 scroll 配合使用可以改变这种行为,enterAlways 起作用时,当手指向下滑动时,Toolbar 会和 NestedScrollView 一起滑动,它们是同时滑动的。

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>

示例效果:
这里写图片描述

enterAlwaysCollapsed

enterAlways 是增强 scroll 动作的,而 enterAlwaysCollapsed 的目的是进一步细化 enterAlways 的行为的。

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:minHeight="100dp"
        app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed" />
</android.support.design.widget.AppBarLayout>

示例效果如下:
这里写图片描述

大家仔细观察一下,enterAlwaysCollapsed 其实也只是 enter 行为,也就是内容下滑的时候。当它 exit 的时候,也就是说手指向上滑动,它和 scroll 保持一致。

关键是 enter 的时候。

因为有 enterAlways 的存在,Toolbar 会和 NestedScrollView 一起响应滑动,但是又因为 enterAlwaysCollapsed 的存在,它的这种行为被限定了。Toolbar 先滑动,等到视图可见范围高度为 collapsed 指定高度时它会静止,等到 NestedScrollView 内容完全显示在 Toolbar 下方时它再一起滑动,它的动作是 3 段式的。

snap

我不知道该怎么翻译这个词,它其实代表了一种场景,要么让你滑动过去要么滑动失败。大家可以参考下 ViewPager 是怎么滑动的,只不过 ViewPager 是水平方向上的,AppBarLayout 中的内容是垂直方向上的行为。

也就是说 snap 代表一种吸附的行为,当一个滑动事件结束后,Toolbar 会向最接近它的边缘自行滚动。那什么是最近的概念呢?比如向上滑动时,如果滑过了一半它就向上滚动,否则滚动回原来的地方。

用示例来说明

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:minHeight="100dp"
        app:layout_scrollFlags="scroll|snap" />
</android.support.design.widget.AppBarLayout>

这里写图片描述

layout_scrollFlags 之间的配合使用

我们前面分析过按照滑动方向,可以分为 enter 和 exit 两种。
而按照每个方向上的行为类别划分,又有不同的行为。
这里写图片描述

其它行为可以自由搭配,比如定制一个 enter 时 enterAlwaysCollapsed,exit 时 exitUntilCollapsed 的行为。

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:minHeight="100dp"
        app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed|exitUntilCollapsed" />
</android.support.design.widget.AppBarLayout>

这里写图片描述

按道理说,因为有 AppBarLayout 的存在,Toolbar 已经多姿多彩了,我们可以利用它们的特性实现很漂亮的 Appbar。

但是,人们仍然不满足,人们希望 Toolbar 的效果能够更加炫丽一些,于是 CollapsingToolbarLayout 这个类出现了。

CollapsingToolbarLayout 的使用

刚刚说过,CollapsingToolbarLayout 出现的目的只是为了增强 Toolbar。

它为 Toolbar 带来了下面几个特性。

  • Collapsing Title 可折叠的标题
  • Content Scrim 内容纱布
  • Status bar scrim 状态栏纱布
  • Parallax scrolling children 子 View 的视差滚动行为
  • Pinned position children 子类的位置固定行为

下面一一为这些内容讲解。

Collapsing title 可折叠的标题

我们新建一个 Activity,简单试验一下,Collapsing Title 是怎么样一个概念。

<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"
    tools:context="com.frank.supportdesigndemo.appbarlayout.CollapsingActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                app:title="title"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                 />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/large_text"></TextView>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

这里写图片描述

可以看到,Collapsing title 的概念其实就是 Title 在大小与位置会变化。
我们再做少许变动。

<android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        app:title="title"
        android:backgroundTint="#f00"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
         />
</android.support.design.widget.CollapsingToolbarLayout>

我们给 Toolbar 添加一个红色的背景色,再观察。
这里写图片描述

我们可以惊奇地发现,Title 竟然没有在 Toolbar 中显示。我们再来看一个现象。

<android.support.design.widget.CollapsingToolbarLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:title="collapsing title"
    app:popupTheme="@style/AppTheme.PopupOverlay"
    app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        app:title="title"
        android:backgroundTint="#f00"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
         />
</android.support.design.widget.CollapsingToolbarLayout>

CollapsingToolbarLayout 与 Toolbar 都定义了 title,结果会以谁的为主呢?
这里写图片描述

显然,CollapsingToolbarLayout 中的 title 覆盖了 Toolbar 中的 title。

需要注意的是 Collapsing title 有两种状态,分别是 展开(Expanded)折叠(Collapsed)

可以运用代码对 Collapsing title 进行如下操作

setTitle(CharSequence title)

setTitleEnabled(boolean enabled)

setCollapsedTitleGravity(int gravity)

setCollapsedTitleTextAppearance(int resId)

setCollapsedTitleTextColor(int color)

setCollapsedTitleTypeface(Typeface typeface)

setExpandedTitleColor(int color)

setExpandedTitleGravity(int gravity)

setExpandedTitleMargin(int start, int top, int end, int bottom)

setExpandedTitleMarginBottom(int margin)
setExpandedTitleMarginEnd(int margin)
setExpandedTitleMarginStart(int margin)
setExpandedTitleMarginTop(int margin)

setExpandedTitleTextAppearance(int resId)

setExpandedTitleTextColor(ColorStateList colors)

setExpandedTitleTypeface(Typeface typeface)

同时,在 xml 布局文件中也可以通过相应的属性操作 Collapsing title 的行为。

app:title
app:titleEnabled

app:collapsedTitleGravity
app:collapsedTitleTextAppearance

app:expandedTitleGravity
app:expandedTitleMargin
app:expandedTitleMarginBottom
app:expandedTitleMarginEnd
app:expandedTitleMarginStart
app:expandedTitleMarginTop
app:expandedTitleTextAppearance

Content scrim 内容纱布

大家看到这个名词第一反应应该会觉得莫名其妙。

现在,我尝试用自己的理解来解释这个东西,真的是自己的理解,不代表我完全正确的,但是我觉得这种理解有助于初学者来理解 Content scrim。

我们先来思考一个词语:交互

我们知道,交互一般是指 动作反馈。比如一个按钮,一般的按钮正常的时候背景色是一种颜色,而按下去的时候,按钮会变化背景颜色,这就是一种反馈。

反馈提示了状态的变化。

还是以按钮为例,手指按下去时,按钮给的反馈是背景颜色发生改变,这种直接的视觉效果差异就是反馈,它提示了按钮从正常状态到按下状态的变化。

那好。现在,我们再来思考 Toolbar。

AppBarLayout 和 CollapsingToolbarLayout 已经提供给 Toolbar 很炫丽的动作效果,但是,如果你认为这还不够,如果你想一种更直接的视觉反馈,这种反馈标志 CollapsingToolbarLayout 中折叠状态的变化时,你可以运用 Content scrim 这个概念。

指定 Contetn scrim 后,CollapsingToolbarLayout 会在折叠状态显示指定的颜色或者是图片,它就像是一块纱布一样遮住 title 下面的内容,所以被称为内容纱布。

注意我的措辞,我说的是 Content scrim 会遮住 title 下方的内容部分。如果一个 CollapsingToolbarLayout 中只有 Toolbar 的话,那么它就不起作用。CollapsingToolbarLayout 本质上是一个 FrameLayout,所以需要在 Toolbar 的前面位置加入其它的 View 作为内容,Content scrim 才会起作用。

<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:title="collapsing title"
            app:contentScrim="#000"
            android:minHeight="100dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/wowan"
                android:scaleType="centerCrop"/>
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                app:title="title"
                app:layout_collapseMode="pin"
                android:layout_width="match_parent"
                android:layout_height="120dp"
                 />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

我们用非常醒目的黑色来充当 Content scrim,下面观察它的表现。

这里写图片描述
Content scrim 除了在 xml 属性中配置外,还可以通过代码设置。

void setContentScrimColor (int color)

void setContentScrimResource (int resId)

void setContentScrim (Drawable drawable)

Status bar scrim 状态栏纱布

这个与 Content scrim 作用类似,不过作用的对象却是在系统的 Statusbar 的位置。

它还有一个特别的地方就是,它只作用在 SDK 5.0 版本之后,并且需要正确配置。

所谓的正确配置其实是说它需要一个 system inset 的矩形,什么意思呢?说的是包裹 CollapsingToolbarLayout 的 AppbarLayout 需要设置 fitsSystemWindows 为 true。

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.design.widget.CollapsingToolbarLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:title="collapsing title"
    app:statusBarScrim="#f00"
    app:contentScrim="#000"
    app:popupTheme="@style/AppTheme.PopupOverlay"
    app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/wowan"
        android:scaleType="centerCrop"/>
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        app:title="title"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
         />
</android.support.design.widget.CollapsingToolbarLayout>

示例效果如下:
这里写图片描述

代码相应的 API

void setStatusBarScrim (Drawable drawable)

void setStatusBarScrimColor (int color)

void setStatusBarScrimResource (int resId)

Parallax scrolling children 子 View 的视差滚动行为

CollapsingToolbarLayout 可以控制的子 View 滚动模式有 3 种:

  • none 默认,无任何效果
  • Parallax 视差滚动
  • pin 固定某个 View

它通过 xml 属性 app:layout_collapseMode 来设置。需要注意的是,这个属性作用对象是 CollapsingToolbarLayout 中的子 View 并不是 CollapsingToolbarLayout。

如何理解视差?就是滚动的速度不同,造成的视觉差异效果。也就是说 CollapsingToolbarLayout 中有的 view 滚动的快一些,其它的滚动的慢一些。
它滚动的快慢受 Parallax multiplier 这个因子的影响,默认值为 DEFAULT_PARALLAX_MULTIPLIER。也就是 0.5f。也就是正常速度的一半。

可以通过 setParallaxMultiplier(float) 方法来设置滚动的速度因子。

<android.support.design.widget.CollapsingToolbarLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:title="collapsing title"
    android:minHeight="100dp"
    app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/wowan"
        android:scaleType="centerCrop"/>
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        app:title="title"
        app:layout_collapseMode="parallax"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:background="#a0ffff00"
         />
</android.support.design.widget.CollapsingToolbarLayout>

示例效果:
这里写图片描述

黄色区域是 Toolbar,可以看到 由于受到 parallax 模式的影响,ImageView 的滚动速度比 Toolbar 要慢。

Pinned position children 子类的位置固定行为

这个很好理解,将 CollapsingToolbarLayout 中某个子 View 固定,无论是否存在滚动事件,只要设置 app:layout_collapseMode=”pin”

<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:title="collapsing title"
android:minHeight="100dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/wowan"
    android:scaleType="centerCrop"/>
<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    app:title="title"
    app:layout_collapseMode="pin"
    android:layout_width="match_parent"
    android:layout_height="120dp"
    android:background="#a0ffff00"
     />
</android.support.design.widget.CollapsingToolbarLayout>

效果如下:
这里写图片描述

可以看到,不管怎么滚动,Toolbar 固定不动。

到这里为止,利用 AppBarLayout 就能实现可折叠的 Toolbar 了。

如果你想快速开发一个这样的界面,可以在 Android Studio 中新建 Activity 的时候选择 Scrolling Activity 模板。

如果你想监听 AppBarLayout 中的滑动位移信息,那么添加相应的监听器就好了。

OnOffsetChangedListener

这是 AppBarLayout 定义的监听器。

void onOffsetChanged (AppBarLayout appBarLayout, int verticalOffset)

verticalOffset 是 AppBarLayout 相对于完全展开时没有滑动的距离。它在初始位置为 0,其它时候都为负数。它绝对值的最大值为 AppBarLayout 的 TotalScollRange。

我们可以根据实际的业务需求,通过监听 AppBarLayout 的位移做一些相应的处理。

比如我可以实现这样的效果。
这里写图片描述

<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:expandedTitleMarginStart="64dp"
        app:contentScrim="?attr/colorPrimary"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">
        <ImageView
            android:id="@+id/circle_icon"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_margin="@dimen/fab_margin"
            android:layout_gravity="bottom"
            app:srcCompat="@android:drawable/ic_dialog_info" />
        <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>

在布局文件中添加一个图标,然后监听 AppBarLayout 的滑动来改变自身的透明度。
相应代码:

final ImageView circleIcon = (ImageView) findViewById(R.id.circle_icon);

AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        int newalpha = 255 + verticalOffset;
        newalpha = newalpha < 0 ? 0 : newalpha;
        circleIcon.setAlpha(newalpha);
    }
});

总结

AppBarLayout 相关其实也不是很复杂,个人的建议是学习的时候先尝试去理解设计者的意图再去查看具体的使用方法。

有兴趣深入的同学可以去模仿市面上主流的 APP,很多都是这种折叠的 Toolbar 效果。

喜欢追根究底的同学可以自己尝试下阅读相关的源码,分析 AppBarLayout 与 CoordinatorLayout 及 Toolbar 的联动机制。

内容较多,有些地方写得仓促,不足之处希望大家指正。

本文相关源码

这里写图片描述

展开阅读全文

没有更多推荐了,返回首页