Hotaru's Notebook

[Android] CoordinatorLayout 基本使用方法

Preface

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

基本概念

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

来对具体的 View 进行布局.

布局文件

比如布局文件是这样的:

<?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)

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

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 类示例

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

#Android #CoordinatorLayout