CoordinatorLayout初探

CoordinatorLayout是Material Design的一个核心布局, 它能起什么作用呢?

从名字上看, 它是帮我们协调子View的, 根据我们的定制要求, 帮助我们协调各个子view的布局.

CoordinatorLayout

我们先来个最简单的例子.


image

在一个布局最右下角增加一个FloatingActionButton悬浮按钮, 点击这个按钮后弹出一个Snackbar.

普通设置如下:

导包

dependencies {
    ...
    implementation 'com.android.support:appcompat-v7:27.0.2'
    implementation 'com.android.support:design:27.0.2'
}

布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    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="com.truly.coordinatorlayoutdemo.MainActivity">
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:layout_gravity="bottom|end"
        android:src="@drawable/ic_add_white_24dp"/>
</FrameLayout>

代码:

private FloatingActionButton fab;
private void initViews() {
    fab = findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Snackbar.make(v,"Add something here",Snackbar.LENGTH_SHORT).show();
        }
    });
}

我们来看下效果.


image

嗯嗯, Snackbar把悬浮按钮挡住了. 我们做一点小小的改动, 将布局中的FrameLayout改为CoordinatorLayout看看.

<android.support.design.widget.CoordinatorLayout
    ...
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.truly.coordinatorlayoutdemo.MainActivity">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        ...
    />

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

现在再来看看效果.


fab.gif

可以看到, Snackbar把悬浮按钮顶起来了.

这就是因为CoordinatorLayout有协调子View的作用. 我们来看一下对它的介绍:

这是一个父控件,继承自ViewGroup,它是加强的FramLayout, 可以协调其它控件并实现控件之间的联动。通过在其直接子View上设置behavior来实现子View的不同交互效果。一般作为一个界面的根布局,来协调AppbarLayout,ToolBarLayout以及ScrollView之间的联动。

那我们没有对FloatingActionButton设置layout_behavior布局行为啊. 这是因为悬浮按钮有一个默认的behavior来检测Snackbar的添加, 并让按钮在Snackbar之上呈现上移与Snackbar等高的动画.

Behavior概念

CoordinatorLayout的使用核心是behavior. 在将behavior之前必须先理解两个概念:

1-Child
2-Dependency

Child当然是子View了, 就是CoordinatorLayout的子View, 更准确的来说, Child是指CoordinatorLayout父布局下要执行动作的子View. 也被称为观察者. 而Dependency是指Child依赖的View. 也被称为被观察者.

简单点说, 就是如果Dependency这个View发生了变化, 那么Child这个View就要发生相应变化. 具体变化就是Behavior引入的.

Child发生变化的具体执行代码都是放在Behavior这个类里面. 怎么使用它呢?

  • 首先定义一个类, 继承CoordinatorLayout.Behavior<T>, 其中泛型参数T是我们要执行动作的View类, 也就是Child
  • 实现Behavior的两个方法:
/**
* 判断child的布局是否依赖dependency
*/ 
    @Override
public boolean layoutDependsOn(CoordinatorLayout parent, T child, View dependency) {
    boolean rs; 
    //根据逻辑判断rs的取值 
    //返回false表示child不依赖dependency,ture表示依赖 
    return rs;
} 

/**
* 当dependency发生改变时(位置、宽高等),执行这个函数 
* 返回true表示child的位置或者是宽高要发生改变,否则就返回false 
*/ 
    @Override 
public boolean onDependentViewChanged(CoordinatorLayout parent, T child, View dependency) {
    //child要执行的具体动作
    return true;
}

我们来写一个简单例子辅助理解Behavior的概念.

简单例子

image

左侧是个Button, 内容特意注明是"Dependency", 右侧是个TextView, 也特意注明了"Child".

我们希望在Button上实现OnTouchListener监听, 在移动这个Button时, Textview做相应的移动.

按照上面所讲, 我们需要定义一个类, 继承CoordinatorLayout.Behavior<T>. 这个Behavior很简单, 就是让child这个TextView在Button下面一点一起移动.

public class MyBehavior extends CoordinatorLayout.Behavior<TextView> {

    //Tip: 必须重写带双参的构造器, 因为从xml反射需要调用
    public MyBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

        @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
        //如果dependency是Button的实例, 说明它就是我们所需要的Dependency
        return dependency instanceof Button;
    }

        @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        //根据dependency的位置, 设置TextView的位置
        child.setX(dependency.getX());
        child.setY(dependency.getY()+200);
        return true;
    }

}

布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    ...>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        .../>

    <Button
        android:id="@+id/btn"
        android:layout_width="120dp"
        android:layout_height="50dp"
        android:background="@color/colorPrimary"
        android:text="Dependency"
        android:textColor="@color/colorAccent"
        android:layout_margin="16dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:textAllCaps="false"/>

    <TextView
        android:id="@+id/view"
        android:layout_width="120dp"
        android:layout_height="50dp"
        android:layout_margin="16dp"
        android:layout_gravity="right"
        android:text="Child"
        android:textAllCaps="false"
        android:gravity="center"
        android:textColor="@color/colorAccent"
        android:background="@android:color/holo_orange_light"
        app:layout_behavior=".MyBehavior"/>

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

注意, 在TextView属性中有一条:

app:layout_behavior=".MyBehavior"

引用的就是我们刚才自己创建的Behavior.

我们还需要在程序中增加一些代码, 让Button动起来.

先是实现Button移动功能的内部类接口.

class MyOnTouch implements View.OnTouchListener {

    int[] temp = new int[]{0, 0};
    Boolean ismove = false;
    int downX = 0;
    int downY = 0;

        @Override
    public boolean onTouch(View v, MotionEvent event) {
        int eventaction = event.getAction();
        int x = (int) event.getRawX();//event.getRawX获取的是绝对位置
        int y = (int) event.getRawY();

        switch (eventaction) {
            case MotionEvent.ACTION_DOWN:
                temp[0] = (int) event.getX();
                temp[1] = y - v.getTop();
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                ismove = false;
                break;
            case MotionEvent.ACTION_MOVE:
                v.layout(x - temp[0], y - temp[1], x + v.getWidth() - temp[0], y - temp[1] + v.getHeight());
                if (Math.abs(downX - x) > 5 || Math.abs(downY - y) > 5)
                ismove = true;
                break;
            case MotionEvent.ACTION_UP:
                if (!ismove)
                    Toast.makeText(MainActivity.this, "你点击了这个按钮", Toast.LENGTH_LONG).show();
                break;
        }
        return false;
    }

}

然后在按钮初始化的时候设置onTouchListener监听.

btn = findViewById(R.id.btn);
btn.setOnTouchListener(new MyOnTouch());

现在可以来看下效果了.


btn_move.gif

可以看到, CoordinatorLayout确实帮我们协调了2个控件的布局行为layout_behavior.

Google在Android 5.0(Lollipop, API 21)开始, 对某些特定控件内置定义了继承自CoordinatorLayout.Behavior的各种Behavior.

如FloatingActionButton.Behavior, AppBarLayout.Behavior等.

后面我们来用用这些系统Behavior, 达成一些有意思的交互效果. 且看下一章.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270