[Android] CoordinatorLayout 基本使用方法

Preface

近些天在做个 Android app demo,有个需求是 默认情况下放大 App 的图标,向上滚动后把它缩小 给下面的内容留出空间。
下面展示的这种效果就是用 CoordinatorLayoutBehavior 来做.

基本概念

CoordinatorLayout,官方文档给出的意思是 “CoordinatorLayout is a super-powered FrameLayout. “,也就是说,直接使用它的话和用 FrameLayout 没有区别。
CoordinatorLayout 与 FrameLayout 的区别就在于:CoordinatorLayout 提供了一个内部类 Behavior,用它可以在运行时动态的调整各个 View 的布局。
通过覆盖 Behavior 的两个方法:

  • public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency)
  • public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)

来对具体的 View 进行布局.

布局文件

比如布局文件是这样的:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?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:background="@android:color/background_light">

<!--
所有 CoordinatorLayout 的第一层 子View 都能对其配置 Behavior,比如在当前
这个配置文件里第一层子View就包含:
AppBarLayout, NestedScrollView, TextView, ImageView.
对这些 View 设置属性 "app:layout_behavior" 为 继承自 CoordinatorLayout.Behavior
的类,CoordinatorLayout 就会在适当的时候调用 Behavior 类的相关方法.
-->

<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:titleEnabled="false">

<Space
android:layout_width="match_parent"
android:layout_height="166dp" />

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
app:layout_collapseMode="pin"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:titleTextColor="@color/colorWhite">

<Space
android:id="@+id/spaceAppIconPlaceholder"
android:layout_width="56dp"
android:layout_height="match_parent" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<android.support.v4.widget.NestedScrollView
android:id="@+id/nestedScrollViewText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:lineSpacingExtra="3sp"
android:text="@string/lorem_ipsum"
android:textColor="@android:color/darker_gray"
android:textSize="18sp" />
</android.support.v4.widget.NestedScrollView>

<TextView
android:id="@+id/textViewAppName"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="3dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:translationZ="6dp"
app:layout_behavior="oing.android.packageviewer.mvc.view.behavior.ToolbarAppNameMoveBehavior"
tools:ignore="RtlHardcoded,UnusedAttribute" />

<!-- 下面将以这个 ImageView 来展示 Behavior 类的用法 -->
<ImageView
android:id="@+id/imageViewAppIcon"
android:layout_width="@dimen/app_icon_size_initial"
android:layout_height="@dimen/app_icon_size_initial"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/content_desc_app_icon"
android:padding="6dp"
android:src="@mipmap/ic_launcher_round"
android:translationZ="6dp"
app:layout_behavior="oing.android.packageviewer.mvc.view.behavior.ToolbarAppIconMoveBehavior"
tools:ignore="UnusedAttribute" />
</android.support.design.widget.CoordinatorLayout>

Behavior

接下来具体说下 Behavior 类的两个主要方法的用法.

boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency)

  • CoordinatorLayot parent
    可以用它来 findViewById(int)
  • View child
    比如上面配置文件里 ImageView 设置了 app:layout_behavior 属性,那么 child 就是这个 ImageView 了.
  • View dependency
    child 所依赖的 View

下面来详细的说一下 childdependency 还有 parent 的关系. dependencyparent 这两个词都是相对于 child 来讲的:

  • 在布局文件中, childparent 包含, 所以 childparent 的 child
  • child 想改变自己的样子(比如位置和大小)就需要根据其它 View 作为参考,也就是依赖其它的 View,也就是 dependency 一词的由来。
    如果有依赖多个 View 的话, 请选择依赖得最亲近的那个.
    有不依赖 dependency 的情况的话,这种情况就忽略 dependency 参数就好了。

layoutDependsOn 这个方法的具体用途只有一个: 判断 child 是否依赖 dependency。每当布局发生变动时,CoordinatorLayout 都会用不同的 View (上面提到的第一层子View)作为 dependency 调用 layoutDependsOn 方法。如果 layoutDependsOn 返回 true,也就意味着你通过覆盖 layoutDependsOn 来告诉 CoordinatorLayout 这个 child 依赖于 dependency,那么下一步 CoordinatorLayout 就会去调用 onDependentViewChanged().

boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
参数的含义和上面的 layoutDependsOn 的参数含义是完全一样的,这个方法的目的是用来写具体的修改 View 的代码。你不需要手动的在 layoutDependsOn 里调用这个方法,CoordinatorLayout 会来调用.

Behavior 类示例

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
package oing.android.packageviewer.mvc.view.behavior;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

public class ToolbarAppIconMoveBehavior extends CoordinatorLayout.Behavior<ImageView> {
// 从布局文件指定到这个类的话, 就使用这样的构造函数.
public ToolbarAppNameMoveBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, ImageView child, View dependency) {
// 用 instanceof 来判断 dependency 是不是 child 想要依赖的 View
return dependency instanceof AppBarLayout;
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, ImageView child, View dependency) {
// 根据 dependency 对 child 做相应的改动, 同时依赖其他的 View 的话也
// 可以用 parent.findViewById(int) 来查找.
child.setX(dependency.getX());
return true;
}
}

References

  1. CoordinatorLayout - developer.android.com
  2. CoordinatorLayout.Behavior - developer.android.com