Android-AppBarLayout

2017/8/26 posted in  Android

Toolbar

Toobar主要是用来替换ActionBar的,换句话说,ActionBar能做的,Toolbar都能做。如果你对ActionBar的使用比较熟悉,你会发现Toolbar使用起来非常简单。ok,既然是替换,当然用Toolbar的时候就得先去把ActionBar给隐藏掉啦~

隐藏ActionBar的方法有很多,可以通过代码的方式隐藏,也可以通过配置文件的方式,我们主要是通过配置文件的方式来隐藏。在我们的styles.xml文件中的AppTheme标签中加入如下两行:

<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>

当然了,你也可以新建一个<style>标签,将上面两行代码加入,并且将这个新建的标签作为<application>theme。还可以选择通过将AppThemeparent设置为Theme.AppCompat.Light.NoActionBar的方式。方法很多,可以自己随便选。

接下来就是将Toolbar放入到布局文件(没啥好解释的):

  <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:background="?attr/colorPrimary"
        android:layout_height="?android:attr/actionBarSize"  />

最后将Toobar作为“ActionBar”来用

 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setTitle("这里是Title");
        toolbar.setSubtitle("这里是子标题");
        toolbar.setLogo(R.drawable.icon); 
        setSupportActionBar(toolbar);

可以对Toolbar设置Logo、标题、子标题等等~还有很多其他的设置,自己去慢慢玩,这里不提啦~。当然了,也可以在布局文件中设置这些,在布局文件设置就不写啦,hongyang大神有篇博客写的挺好的《 Android 5.x Theme 与 ToolBar 实战 》可以去参考一下。

如果Toolbar仅仅是用来对以往的ActionBar做一次替换,那也太没创意啦!完全没必要去替换了,因为它们表现出来的都是一样的,而且并没有让我们觉得用起来比ActionBar方便。那为啥要替换呢,总应该有他的理由吧:ActionBar是固定在顶部,并不能移动,我觉得这是最大的不好,而我们的ToolBar可以让我们随便摆放,就就可以带来很多灵活性和效果啦!

正如你所看的这样,Toolbar根本就不够看的,一点都不复杂。接下来我们继续学习在Toolbar上面再套一层父View,让Toolbar更有互动性。

AppBarLayout

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

请注意:上面提到的某个可滚动View,可以理解为某个ScrollView。怎么理解上面的话呢?就是说,当某个ScrollView发生滚动时,你可以定制你的“顶部栏”应该执行哪些动作(如跟着一起滚动、保持不动等等)。那某个可移动的View到底是哪个可移动的View呢?这是由你自己指定的!如何指定,我们后面说。

AppBarLayout子View的动作

内部的子View通过在布局中加app:layout_scrollFlags设置执行的动作,那么app:layout_scrollFlags可以设置哪些动作呢?分别如下:

(1) scroll:值设为scroll的View会跟随滚动事件一起发生移动。

什么意思呢?简单的说,就是当指定的ScrollView发生滚动时,该View也跟随一起滚动,就好像这个View也是属于这个ScrollView一样。

一张gif足以说明:

20171014150799488071606.gif
20171014150799488071606.gif

对应的布局文件

<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="?android:attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll" />
</android.support.design.widget.AppBarLayout>

(2) enterAlways:值设为enterAlways的View,当ScrollView往下滚动时,该View会直接往下滚动。而不用考虑ScrollView是否在滚动。

看个动画片(Y(o)Y)(ToolBar高度设为:?android:attr/actionBarSize,app:layout_scrollFlags="scroll|enterAlways"):

20171014150799493830006.gif
20171014150799493830006.gif

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

怎么理解呢?简单解释:在ScrollView往上滑动时,首先是View把滑动事件“夺走”,由View去执行滑动,直到滑动最小高度后,把这个滑动事件“还”回去,让ScrollView内部去上滑。看个gif感受一下(图中将高度设的比较大:200dp,并将最小高度设置为?android:attr/actionBarSize,app:layout_scrollFlags="scroll|exitUntilCollapsed"):

20171014150799499853731.gif
20171014150799499853731.gif

(4) enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是enterAlways效果,当View的高度达到最小高度时,View就暂时不去往下滚动,直到ScrollView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束。
来个gif感受一下(图中将高度设的比较大:200dp,并将最小高度设置为?android:attr/actionBarSize,app:layout_scrollFlags="scroll|enerAlways|enterAlwaysCollapsed"):

20171014150799505696205.gif
20171014150799505696205.gif

将AppBarLayout与ScrollView关联起来

前面说了一直反复说“当ScrollView发生滚动时”,那么怎么将AppBarLayout与ScrollView关联起来呢?我们注意到,AppBarLayout与ScrollView之间动作“相互依赖”,这不就是我们上一篇《CoordinateLayout的使用如此简单 》所学的内容吗?把ScrollView和AppBarLayout作为CoordinateLayout的子View,然后编写一个Behavior,在这个Behavior里面判断当前的操作是应该让ScrollView时刻保持在AppBarLayout之下(即只要改变AppBarLayout的位置就可以一起滑动),还是应该让ScrollView内部滚动而不让AppBarLayout位置发生变化等等这些需求,都是可以在Behavior里面处理的。你可以去针对你的ScrollView编写Behavior。然而,我们看到我们的AppBarLayout事先的功能比较复杂,如果我们自己去定义这样的效果,代码非常复杂,还要考虑很多方面,好在Android帮我们写好啦,我们直接用就是了,这个ScrollView就是NestedScrollView,请注意,它并没有继承ScrollView,它继承的是FrameLayout,但是它实现的效果把它可以看成是ScrollView。

把NestedScrollView放入到我们的layout文件里面就可以啦~~~,很方便~

 <android.support.v4.widget.NestedScrollView

        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

       <!--将你的内容放在这里-->

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

有没有注意到有个属性:app:layout_behavior="@string/appbar_scrolling_view_behavior",它就是指定Behavior的,appbar_scrolling_view_behavior对应的类的名称是:android.support.design.widget.AppBarLayout$ScrollingViewBehavior感兴趣的可以去分析源码。

好了,我们现在会用AppBarLayout啦~是不是发现用起来so easy!接下来我们把剩下CollapsingToolbarLayout的给"消化"掉!

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)来设置纱布的图片.

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

(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 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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/main.backdrop"
                android:layout_width="wrap_content"
                android:layout_height="300dp"
                android:scaleType="centerCrop"
                android:src="@drawable/material_img"
                app:layout_collapseMode="parallax" />

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

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

    <android.support.v4.widget.NestedScrollView

        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="50dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/my_txt"
            android:textSize="20sp" />

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

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

上面的都看得懂吧,每个陌生的属性都是讲过的哦,忘记了的话回头看,稍微解释一下,图片被设置为有视差的滑动,Toolbar设置为固定不动,另外,CollapsingToolbarLayout会对title进行放大和缩小,我们看看效果吧~

20171014150799532425417.gif
20171014150799532425417.gif

如果你希望拖动过程中状态栏是透明的,可以在CollapsingToolbarLayout中加 app:statusBarScrim="@android:color/transparent",并且在onCreate中调用getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)将状态栏设置为透明就好啦~

献上源码

AppBarLayout的ScrollFlags属性

在前面两篇MD系列的文章中,通过两个案例基本上能够掌握了CoordinatorLayout与AppBarLayout的配合使用。本文我们回过头来详细聊聊AppBarLayout的ScrollFlags属性,了解一下不同值之间的区别。至此,Android Material Design系列的学习已进行到第七篇,大家可以点击以下链接查看之前的文章:

ScrollFlags共有五种常量值供AppBarLayout的Children View使用,在xml布局文件中通过app:layout_scrollFlags设置,对应的值为:scroll,enterAlways,enterAlwaysCollapsed,snap,exitUntilCollapsed;也可以在代码中通过setScrollFlags(int)方法使用,比如:

Toolbar toolbar = ... // your toolbar within an AppBarLayout
AppBarLayout.LayoutParams params = 
    (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
    | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);

下面我们通过官网介绍、XML代码和对应的效果图分别分析这五种值的使用(备注:代码中设置也一样,不再赘述):

scroll

The view will be scroll in direct relation to scroll events. This flag needs to be set for any of the other flags to take effect. If any sibling views before this one do not have this flag, then this value has no effect.

Child View 伴随着滚动事件而滚出或滚进屏幕。注意两点:第一点,如果使用了其他值,必定要使用这个值才能起作用;第二点:如果在这个child View前面的任何其他Child View没有设置这个值,那么这个Child View的设置将失去作用。

示例XML代码:

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/tb_toolbar"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_56"
            app:titleTextColor="@color/white"
            app:title="@string/app_name"
            app:theme="@style/OverFlowMenuTheme"
            app:popupTheme="@style/AppTheme"
            android:background="@color/blue"
            app:layout_scrollFlags="scroll|enterAlways"/>

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

对应效果图:

20171019150842247229844.gif
20171019150842247229844.gif

enterAlways

When entering (scrolling on screen) the view will scroll on any downwards scroll event, regardless of whether the scrolling view is also scrolling. This is commonly referred to as the 'quick return' pattern.

快速返回模式。其实就是向下滚动时Scrolling View和Child View之间的滚动优先级问题。对比scrollscroll | enterAlways设置,发生向下滚动事件时,前者优先滚动Scrolling View,后者优先滚动Child View,当优先滚动的一方已经全部滚进屏幕之后,另一方才开始滚动。

示例XML代码:

...
app:layout_scrollFlags="scroll|enterAlways"
...

对应效果图:

20171019150842251698266.gif
20171019150842251698266.gif

enterAlwaysCollapsed

An additional flag for 'enterAlways' which modifies the returning view to only initially scroll back to it's collapsed height. Once the scrolling view has reached the end of it's scroll range, the remainder of this view will be scrolled into view. The collapsed height is defined by the view's minimum height.

enterAlways的附加值。这里涉及到Child View的高度和最小高度,向下滚动时,Child View先向下滚动最小高度值,然后Scrolling View开始滚动,到达边界时,Child View再向下滚动,直至显示完全。

示例XML代码:

...
android:layout_height="@dimen/dp_200"
android:minHeight="@dimen/dp_56"
...
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
...

对应效果图:

20171019150842256217688.gif
20171019150842256217688.gif

exitUntilCollapsed

When exiting (scrolling off screen) the view will be scrolled until it is 'collapsed'. The collapsed height is defined by the view's minimum height.

这里也涉及到最小高度。发生向上滚动事件时,Child View向上滚动退出直至最小高度,然后Scrolling View开始滚动。也就是,Child View不会完全退出屏幕。

示例SML代码:

...
android:layout_height="@dimen/dp_200"
android:minHeight="@dimen/dp_56"
...
app:layout_scrollFlags="scroll|exitUntilCollapsed"
...

对应效果图:

20171019150842260265572.gif
20171019150842260265572.gif

snap

Upon a scroll ending, if the view is only partially visible then it will be snapped and scrolled to it's closest edge. For example, if the view only has it's bottom 25% displayed, it will be scrolled off screen completely. Conversely, if it's bottom 75% is visible then it will be scrolled fully into view.

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

示例XML代码:

...
android:layout_height="@dimen/dp_200"
...
app:layout_scrollFlags="scroll|snap"
...

对应效果图:

20171019150842265497458.gif
20171019150842265497458.gif

示例源码

我在GitHub上建立了一个Repository,用来存放整个Android Material Design系列控件的学习案例,会伴随着文章逐渐更新完善,欢迎大家补充交流,Star地址:

https://github.com/Mike-bel/MDStudySamples