本人很喜欢Material Design 里面的折叠效果,趁着有时间做个demo,学习下CollapsingToolbarLayout的使用。
先来看下效果:
首先我们来分析下,实现该功能需要做些什么?
- 初始状态下,当界面滑动的时候,首先是顶部的布局慢折叠,折叠到一定高度的时候停止折叠,折叠过程中字体和背景色慢慢发生变化。
- 当停止折叠后,手势继续向上滑动,底部的滑动控件可以继续向上滑;手势向下滑动的时候,当底部的滑动控件滑到顶的时候,再向下滑动,背景图就会慢慢展开到初始高度。
按照以前的做法,实现第一个功能,我们需要监听滑动滑动事件,然后改变顶部布局、title的相关参数;
实现第二个功能就比较麻烦了,整个滑动过程是连续的,所以我们需要重写父布局的事件传递机制,判断 ACTION_MOVE 事件应该由哪个子View来处理,当底部的滑动控件向下滑动到顶部时,又需要将事件传递到顶部的布局,整个过程处理起来比较麻烦。
有没有比较方便的方法实现呢?当然有了,它就是今天我们要介绍的Android的新特性,CoordinatorLayout、AppBarLayout和 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"
tools:context=".widget.CollapsingToolbarActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="200dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collaps_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/bg_head"
app:collapsedTitleGravity="center"
app:collapsedTitleTextAppearance="@style/CollapsTextAppearance"
app:contentScrim="@color/colorPrimaryDark"
app:expandedTitleGravity="center"
app:expandedTitleTextAppearance="@style/ExPandTextAppearance"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:scrimAnimationDuration="1000"
app:statusBarScrim="@android:color/black"
app:title="CollapsToolBar"
app:titleEnabled="true">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="60dp"
android:visibility="visible"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin">
</android.support.v7.widget.Toolbar>
<ImageView
android:layout_width="wrap_content"
android:src="@mipmap/icon_arrow_left"
android:paddingTop="18dp"
android:paddingLeft="18dp"
app:layout_collapseMode="pin"
android:paddingStart="18dp"
android:layout_height="wrap_content"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="30dp"
android:src="@mipmap/ic_launcher"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="right|bottom|end"/>
</android.support.design.widget.CoordinatorLayout>
一、CollapsingToolBarLayout
CollapsingToolBarLayout 提供了一个可折叠的ToolBar布局,主要的属性有:
- app:title :设置CollapsingToolBarLayout 的title显示的内容,要显示title,必须先设置 app:titleEnabled="true"(默认该属性是false),而且 CollapsingToolBarLayout 的子View中要包含一个ToolBar,否则不会显示出来;
- app:contentScrim:设置折叠之后 CollapsingToolBarLayout 的背景色;
- app:collapsedTitleXXX : collapsedTitleXXX 系列属性值用于设置 折叠之后的title 的样式和位置;
- app:expandedTitleXXX : expandedTitle 系列属性值用于设置 未折叠之前的 title 的样式和位置;
- app:scrimAnimationDuration:折叠变换动画的执行时间
- app:statusBarScrim:设置折叠之后 statusBar 的背景色-------------------------还有疑问
- layout_collapseParallaxMultiplier :不折叠视差系数,配合parallax模式使用,取值有点类似alpha(不透明度),在0.0 ~ 1.0之间,默认值为0.5。当设置为1.0,滚动列表时图片不会折叠移动
以上是 CollapsingToolBarLayout 本身的属性值,从属性的作用我们可以看出 CollapsingToolBarLayout 本身的属性可以完成背景图,title的变化效果的设置。从整体的布局我们看到 CollapsingToolBarLayout 的子布局 ToolBar有个属性
app:layout_collapseMode="pin"
layout_collapseMode 折叠模式,用于指定 CollapsingToolBarLayout 折叠之后该View的行为,有三个值可选:
- pin : 表示 CollapsingToolBarLayout 折叠之后,该View固定不动
- none : 默认行为,会随着 CollapsingToolBarLayout 折叠而消失
- parallax :设置为这个模式时,在内容滚动时,CollapsingToolbarLayout中的View(比如ImageView)也可以同时滚动,
实现视差滚动效果,通常和CollapsingToolBarLayout 的属性 layout_collapseParallaxMultiplier(设置视差因子)搭配使用
我们可以用该模式来实现折叠之后显示的蓝色背景的ToolBar ,有人可能会问 CollapsingToolbarLayout 布局下为什么还要加一个 ToolBar 控件,用其他布局可以吗?可以的,只不过 CollapsingToolbarLayout 自带的title 要显示出来的有两个条件是必须的:
- app:titleEnabled="true"(默认该属性是false)。
- CollapsingToolbarLayout 子View要有一个ToolBar。CollapsingToolbarLayout 的 title 在折叠过程中会有移动,大小,颜色等变换效果,最终是移动到 ToolBar上的,所以我们在 CollapsingToolbarLayout 下添加一个 ToolBar。
到这里我们发现 CollapsingToolBarLayout 默认已经具备实现第一个需求的条件了,但是这样设置一定会执行吗?当然。。不是啦,聪明如你,我想已经发现
app:layout_scrollFlags="scroll|exitUntilCollapsed"
这个属性是做什么的?CollapsingToolBarLayout 的自定义属性可没有它呀,那么只有一个可能就是属性来自他的父布局 AppBarLayout
二、AppBarLayout
layout_scrollFlags 指定 AppBarLayout 的子View 滚动模式,可选值有:
- scroll : 要想滚动必须设置该模式
- enterAlways :实现quick return效果,向下滑动的时候,快速显示view
- enterAlwaysCollapsed :当你的View已经设置minHeight属性又使用此标志时,向下滑动到 minHeight 的高度之后,才会开始显示view
- exitUntilCollapsed :向上滚动时收缩View,但可以固定Toolbar一直在上面
- snap :子 View不会存在局部显示的情况,滚动子View的部分高度,当我们松开手指时,子View要么向上全部滚出屏幕,要么向下全部滚进屏幕
可以设置多个值,比如:
app:layout_scrollFlags="scroll|exitUntilCollapsed"
这样第一个需求就差不多可以完成了,但是 AppBarLayout 不会自己去滚动呀,要有人来推一把的。这就要认识下第三位兄弟 CoordinatorLayout 了。
三、CoordinatorLayout
协调布局,协调(Coordinate)其他组件, 实现联动的布局。只有最外层是这个布局,一些滚动行为的参数才能使用。比如说:
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
app:layout_behavior 这个属性,这个属性是用来做什么的呢?这个属性就是用来协调各个组件的关键,behavior 就是用来定义一种行为,当带有该属性的组件滚动的时候,各个组件随之作出的行为。就比如说 CollapsingToolBarLayout 收缩,收缩到一定高度的时候RecyclerView继续滚动。我们来看下今天的例子:
看到这里我相信大家都会有疑问:
- 是不是无论什么控件(或者是可以滑动的控件)设置 layout_behavior 属性,都可以实现这样的效果?换句话说就是什么控件设置 layout_behavior 属性才可以实现联动的效果?
- 直接使用 CollapsingToolBarLayout 不行吗,为什么还要套一层AppBarLayout,套其他的布局可以吗?
我们先来看第一个问题,这个问题和 NestedScrolling机制 有关,关于这个机制可以去看下鸿阳大神关于该机制的解析:
Android NestedScrolling机制完全解析 带你玩转嵌套滑动
我们看下 CoordinatorLayout 的实现:
了解过NestedScrolling机制之后,是不是觉得很眼熟,我们再来看下 RecyclerView的实现:
是不是有种恍然大悟的感觉,没错 CoordinatorLayout 协调的原理就是来自NestedScrolling机制,所以 layout_behavior 是设置在实现了 NestedScrollingChild2 接口的滑动控件中,当RecyclerView发生滑动事件时,会将滑动事件交由 CoordinatorLayout 进行统一协调。
我们再来看第二个问题,从第一个问题中我们知道了当 RecyclerView 滑动时 AppBarLayout 会跟着变化,原因在于我们设置的 layout_behavior 的属性值 @string/appbar_scrolling_view_behavior ,我们深入去看下:
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
接着到指定的目录下找到 ScrollingViewBehavior 这个类:
public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
public ScrollingViewBehavior() {}
public ScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ScrollingViewBehavior_Layout);
setOverlayTop(a.getDimensionPixelSize(
R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
a.recycle();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
// We depend on any AppBarLayouts
return dependency instanceof AppBarLayout;
}
只截出部分代码,有兴趣的小伙伴可以去看看源码。了解 NestedScrolling机制的你,一眼就能看出 layoutDependsOn 这个方法做了判断,只处理 AppBarLayout 。因为我们使用了 AppBarLayout 的默认行为 AppBarLayout$ScrollingViewBehavior,所以要套一层 AppBarLayout。我们来看下 ScrollingViewBehavior:
嗯,好像知道了。我们实现 CoordinatorLayout$Behavior 接口,就可以自定义behavior啦。