CoordinatorLayout是Material Design的一个核心布局, 它能起什么作用呢?
从名字上看, 它是帮我们协调子View的, 根据我们的定制要求, 帮助我们协调各个子view的布局.
CoordinatorLayout
我们先来个最简单的例子.
在一个布局最右下角增加一个FloatingActionButton悬浮按钮, 点击这个按钮后弹出一个Snackbar.
普通设置如下:
导包
dependencies {
...
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support:design:27.0.2'
}
布局:
代码:
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();
}
});
}
我们来看下效果.
嗯嗯, Snackbar把悬浮按钮挡住了. 我们做一点小小的改动, 将布局中的FrameLayout改为CoordinatorLayout看看.
现在再来看看效果.
可以看到, 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是我们要执行动作的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的概念.
简单例子
左侧是个Button, 内容特意注明是"Dependency", 右侧是个TextView, 也特意注明了"Child".
我们希望在Button上实现OnTouchListener监听, 在移动这个Button时, Textview做相应的移动.
按照上面所讲, 我们需要定义一个类, 继承CoordinatorLayout.Behavior
public class MyBehavior extends CoordinatorLayout.Behavior {
//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;
}
}
布局
注意, 在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());
现在可以来看下效果了.
可以看到, CoordinatorLayout确实帮我们协调了2个控件的布局行为layout_behavior.
Google在Android 5.0(Lollipop, API 21)开始, 对某些特定控件内置定义了继承自CoordinatorLayout.Behavior的各种Behavior.
如FloatingActionButton.Behavior, AppBarLayout.Behavior等.
后面我们来用用这些系统Behavior, 达成一些有意思的交互效果. 且看下一章.