Coordinatorlayout使用全攻略

Catalogue
  1. 1 自定义Behavior
    1. 1.1 基础概念
  2. 2 CoordinatorLayout使用
    1. 2.1 Floating Action Buttons 和 Snackbars
    2. 2.2 Expanding 和 Collapsing Toolbars
    3. 2.3 创建折叠效果
    4. 2.4 Bottom Sheet
  3. 参考资料

1 自定义Behavior

1.1 基础概念

其实Behavior就是一个应用于View的观察者模式,一个View跟随者另一个View的变化而变化,或者说一个View监听另一个View。
在Behavior中,被观察View也就是事件源被称为denpendcy,而观察View,则被称为child。
Button与TextView的爱恨情仇
首先在布局文件中跟布局设置为CoordinatorLayout,里面放一个Button和一个TextView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout    android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<TextView
app:layout_behavior=".EasyBehavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="观察者View child"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="被观察View dependency"
/>
</android.support.design.widget.CoordinatorLayout>

这里我们在Activity中做一些手脚,让Button动起来(不要在意坐标这些细节)

1
2
3
4
5
6
7
8
9
10
11
12
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_easy_behavior);
findViewById(R.id.btn).setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_MOVE:
v.setX(event.getRawX()-v.getWidth()/2);
v.setY(event.getRawY()-v.getHeight()/2); break;
} return false;
}
});

}

此时,Button已经可以跟随手指移动了。
现在去自定义一个Behavior让TextView跟随Button一起动!
创建一个EasyBehavior类,继承于Behavior

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EasyBehavior extends CoordinatorLayout.Behavior<TextView> {//这里的泛型是child的类型,也就是观察者View
public EasyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) { //告知监听的dependency是Button
return dependency instanceof Button;
}

@Override //当 dependency(Button)变化的时候,可以对child(TextView)进行操作
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
child.setX(dependency.getX()+200);
child.setY(dependency.getY()+200);
child.setText(dependency.getX()+","+dependency.getY()); return true;
}
}

注意两个方法
layoutDependsOn() 代表寻找被观察View
onDependentViewChanged() 被观察View变化的时候回调用的方法
在onDependentViewChanged中,我们让TextView跟随Button的移动而移动。代码比较简单,一看就懂。
Tip
必须重写带双参的构造器,因为从xml反射需要调用。
接下来,在xml中,给TextView设置我们的Behavior。

1
2
3
4
5
6
<TextView      
app:layout_behavior=".EasyBehavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="观察者View child"
/>

运行效果如下:

2 CoordinatorLayout使用

CoordinatorLayout是谷歌提供的Material Design许多功能的基础,使用它你可以轻松地实现许多动画效果。这些效果包括:
1.滑动Floating Action Button为SnackBar的弹起提供空间。

2.展开或者收起Toolbar为屏幕的主内容提供空间。

3.控制某个应该被收起或展开的View以一定地视差效果完成动作,包括parallax scrolling effects 动画。

2.1 Floating Action Buttons 和 Snackbars

FloatingActionButton可以通过layout_anchor和layout_anchorGravity来控制显示的位置。当我们将其显示到屏幕的底端时,它会自动响应SnackBar的弹起或收起:SnackBar弹起后FloatingActionButton也弹起SnackBar的高度,收起后FloatingActionButton也收起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<android.support.design.widget.CoordinatorLayout
android:id="@+id/main_content"
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.v7.widget.RecyclerView
android:id="@+id/rvToDoList"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
android:src="@mipmap/ic_launcher"
app:layout_anchor="@id/rvToDoList"
app:layout_anchorGravity="bottom|right|end"/>
</android.support.design.widget.CoordinatorLayout>

2.2 Expanding 和 Collapsing Toolbars

1.确保CoordinatorLayout是主容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

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

2.为了响应滑动事件,我们必须使用AppBarLayout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="@dimen/detail_backdrop_height"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

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

为了让滑动的View始终在AppBarLayout的下面,需要为滑动的View定义layout_behavior为@string/appbar_scrolling_view_behavior。我们还可以通过app:layout_scrollFlags控制AppBarLayout中的子View响应RecycleView的滑动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<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.v7.widget.RecyclerView
android:id="@+id/rvToDoList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.AppBarLayout>

关于layout_scrollFlags的值这里解释一下:

  1. scroll - 想滚动就必须设置这个。
  2. enterAlways - 实现quick return效果, 当向下移动时,立即显示View(比如Toolbar)。
  3. exitUntilCollapsed - 向上滚动时收缩View,但可以固定Toolbar一直在上面(可通过minHeight设置)。
  4. enterAlwaysCollapsed - 当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。
  5. snap - 如果头部滑动操作超过50%的距离,则自动关闭。如果头部滑动操作不超过50%的距离,则不关闭。

2.3 创建折叠效果

CollapsingToolbarLayout作用是提供了一个可以折叠的Toolbar,它继承至FrameLayout,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<android.support.design.widget.AppBarLayout  
android:layout_width="match_parent"
android:layout_height="256dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="#30469b"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/bg"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7" />

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
<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:scrollbars="none" />
</android.support.design.widget.AppBarLayout>

1、在CollapsingToolbarLayout中设置了一个ImageView和一个Toolbar。并把这个CollapsingToolbarLayout放到AppBarLayout中作为一个整体。
在CollapsingToolbarLayout中,我们除了设置layout_scrollFlags,还可以设置一些其它的属性,简要说明一下:

  • contentScrim - 设置当完全CollapsingToolbarLayout折叠(收缩)后的背景颜色。
  • expandedTitleMarginStart - 设置扩张时候(还没有收缩时)title向左填充的距离。

没扩张时候如图:

2、在ImageView控件中:
我们设置了layout_collapseMode (折叠模式) - 有两个值:

  • pin - 设置为这个模式时,当CollapsingToolbarLayout完全收缩后,Toolbar还可以保留在屏幕上。
  • parallax - 设置为这个模式时,在内容滚动时,CollapsingToolbarLayout中的View(比如ImageView)也可以同时滚动,实现视差滚动效果,通常和layout_collapseParallaxMultiplier(设置视差因子)搭配使用。
  • layout_collapseParallaxMultiplier(视差因子) - 设置视差滚动因子,值为:0~1。

3、在Toolbar控件中,我们设置了layout_collapseMode(折叠模式)为pin。
效果如图:

综上分析:当设置了layout_behavior的控件响应起了CollapsingToolbarLayout中的layout_scrollFlags事件时,ImageView会有视差效果的向上滚动移除屏幕,当开始折叠时CollapsingToolbarLayout的背景色(也就是Toolbar的背景色)就会变为我们设置好的背景色,Toolbar也一直会固定在最顶端。

注意:
使用CollapsingToolbarLayout时必须把title设置到CollapsingToolbarLayout上,设置到Toolbar上不会显示。即:mCollapsingToolbarLayout.setTitle(“ “);

2.4 Bottom Sheet

有两种类型的Bottom Sheet
1.Persistent bottom sheet : 通常用于显示主界面之外的额外信息,它是主界面的一部分,只不过默认被隐藏了,其深度(elevation)跟主界面处于同一级别;还有一个重要特点是在Persistent bottom sheet打开的时候,主界面仍然是可以操作的。ps:Persistent bottom sheet该如何翻译呢?我觉得翻译为普通bottom sheet就好了,还看到有人翻译为“常驻bottom sheet”,可能更接近于英语的字面意思,可是反而不易理解。

2.模态bottom sheet : 顾名思义,模态的bottom sheet在打开的时候会阻止和主界面的交互,并且在视觉上会在bottom sheet背后加一层半透明的阴影,使得看上去深度(elevation)更深。
总结起来这两种Bottom Sheet的区别主要在于视觉和交互上,当然使用方法也是不一样的。


参考资料

CoordinatorLayout介绍