官方文档对CoordinatorLayout是这样描述的:
CoordinatorLayout是一个“加强版”FrameLayout,它主要有两个用途:
1. 用作应用的顶层布局管理器,也就是作为用户界面中所有UI控件的容器
2. 用作相互之间距有特定交互行为的UI控件的容器
通过为CoordinatorLayout的子View指定Behavior,就可以实现它们之间的交互行为。
Behavior可以用来实现一系列的交互行为和布局变化,比如说侧滑菜单、可滑动删除的UI元素,以及跟随着其他UI控件移动的按钮等。
上面的描述可能有些抽象,现在我们只需要知道CoordinatorLayout是一个布局管理器,主要用来实现它的子View间的交互行为。那么什么是交互行为呢?我们来看一个简单的例子:
在上图中,我们拖动按钮,可以看到,一个TextView会跟着按钮一起移动。这就是交互行为的一个简单地例子。也就是说首先需要Button的位置发生变化,然后TextView对Button的位置变化做出响应,这个例子TextView做出响应的方式就是跟随着Button一起移动。那么这个交互行为时如何实现的呢?我们接着往下看。
上面我们看到了一个简单地交互行为的例子,下面我们通过分析这个例子来介绍一下实现交互行为的一般性步骤。
上面的例子实现的是Button和TextView的交互行为:Button发生变化时,TextView要对这个变化做出响应。那么首先我们要让TextView知道Button产生了变化,还要指明TextView对Button的变化做出什么反应。
这里实际上是一个观察者模式的运用:TextView是观察者,Button是被观察者。TextView需要向系统注册一个回调,告知系统Button发生变化时通知它,这样当Button发生变化时,TextView会得到关于这个变化的通知,这样就可以对这个变化做出反应。如此一来我们便实现了TextView和Button的交互行为。下面我们来介绍如何通过给TextView设置一个Behavior来实现上面的交互行为。
首先我们先来看一下用户界面的布局文件:
我们为TextView指定了一个layout_behavior属性,这样就给它设置了一个Behavior。我们可以看到layout_behavior属性的值为".FollowBehavior",指的是当前Module中一个名为FollowBehavior的类,它实际上是CoordinatorLayout.Behavior类的子类,我们来看一下FollowBehavior类的实现。
FollowBehavior类的代码如下:
public class FollowBehavior extends CoordinatorLayout.Behavior {
public FollowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof Button;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
child.setX(dependency.getX() + 150);
child.setY(dependency.getY() + 150);
return true;
}
}
在上面的代码中我们重写了父类的两个方法:layoutDependsOn()方法和onDependentViewChanged()方法。在介绍这两个方法的作用前,我们先来介绍一下dependent view。在一个交互行为中,dependent view的变化决定了另一个相关View的行为。在这个例子中,Button就是dependent view,因为TextView跟随着它。实际上dependent view就相当于我们前面介绍的被观察者。知道了这个概念,让我们看看重写的两个方法的作用:
现在我们已经定义好了一个交互行为,但是Button还不会跟着我们的手指移动,接下来我们让它动起来。
我们只需让Button在屏幕上的位置随我们手指移动,从而让TextView跟着它移动,相关代码如下:
public class CoorDemoActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.follow);
findViewById(R.id.btn).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
v.setX(event.getRawX()-v.getWidth()/2);
v.setY(event.getRawY()-v.getHeight()/2);
}
return true;
}
});
}
}
这样一来,我们就完成了为TextView和Button设置跟随移动这个交互行为。很简单有木有,其实为CoordinatorLayout的子View设置交互行为只需三步:
值得注意的是,有些时候,并不需要我们自己来定义一个Behavior类,因为系统为我们预定义了不少Behavior类。在接下来的篇章中,我们会做出进一步的介绍。
现在我们已经知道了怎么通过给CoordinatorLayout的子View设置Behavior来实现交互行为。现在,让我们更进一步地挖掘下CoordinatorLayout,深入了解一下隐藏在表象背后的神秘细节。
实际上,CoordinatorLayout本身并没有做过多工作,实现交互行为的主要幕后推手是CoordinatorLayout的内部类——Behavior。通过为CoordinatorLayout的直接子View绑定一个Behavior,这个Behavior就会拦截发生在这个View上的Touch事件、嵌套滚动等。不仅如此,Behavior还能拦截对与它绑定的View的测量及布局。关于嵌套滚动,我们会在后续文章中进行详细介绍。下面我们来深入了解一下Behavior是如何做到这一切的。
拦截Touch事件
当我们为一个CoordinatorLayout的直接子View设置了Behavior时,这个Behavior就能拦截发生在这个View上的Touch事件,那么它是如何做到的呢?实际上,CoordinatorLayout重写了onInterceptTouchEvent()方法,并在其中给Behavior开了个后门,让它能够先于View本身处理Touch事件。具体来说,CoordinatorLayout的onInterceptTouchEvent()方法中会遍历所有直接子View,对于绑定了Behavior的直接子View调用Behavior的onInterceptTouchEvent()方法,若这个方法返回true,那么后续本该由相应子View处理的Touch事件都会交由Behavior处理,而View本身表示懵逼,完全不知道发生了什么。
拦截测量及布局
了解了Behavior是怎养拦截Touch事件的,想必大家已经猜出来了它拦截测量及布局事件的方式——CoordinatorLayout重写了测量及布局相关的方法并为Behavior开了个后门。没错,真相就是如此。
CoordinatorLayout在onMeasure()方法中,会遍历所有直接子View,若该子View绑定了一个Behavior,就会调用相应Behavior的onMeasureChild()方法,若此方法返回true,那么CoordinatorLayout对该子View的测量就不会进行。这样一来,Behavior就成功接管了对View的测量。
同样,CoordinatorLayout在onLayout()方法中也做了与onMeasure()方法中相似的事,让Behavior能够接管对相关子View的布局。
View的依赖关系的确定
现在,我们在探究一下交互行为中的两个View之间的依赖关系是怎么确定的。我们称child为交互行为中根据另一个View的变化做出响应的那个个体,而dependent view为child所依赖的View。实际上,确立child和dependent view的依赖关系有两种方式:
无论是隐式依赖还是显式依赖,在dependent view发生变化时,相应Behavior类的onDependentViewChanged()方法都会被调用,在这个方法中,我们可以让child做出改变以响应dependent view的变化。
链接:https://www.jianshu.com/p/b81f5e0d3241