此篇文章需要用到之前的知识点,即我之前写的这两篇博客
​​​事件分发机制原理分析​​​​NestedScrolling机制原理分析​

一.CoordinatorLayout基本介绍

1.使用场景

一般作为应用的顶层布局,同时也作为一个管理容器,管理与子view​ 或者 view之间的交互。那么都具体管理啥呢?可以分成四个部分

2.作用

  • 处理子控件之间依赖下的交互
  • ②处理子控件之间的嵌套滑动
  • ③处理子控件的测量与布局
  • ④处理子控件的事件拦截与响应

以上四个功能,都建立于​​CoordainatorLayout​​​中提供的一个叫做​​Behavior​​的**“ 插件”**之上。Behavior内部也提供了相应方法来对应这四个不同的功能。那么都有哪些方法呢?

3.Behavior中的常用方法

Material Design之CoordinatorLayout原理剖析_控件

4.为什么把这些方法都放到Behavior中呢?

答案:解耦。当用的时候就集成进去,不用的时候就​​remove​​。即可插拔

  • 在这里可以把​​CoordinatorLayout​​​比喻成Android Studio,把子​​View​​​比喻成我们的项目。我们知道,​​AS​​​可以使用​​Plugins​​​为项目引入各种插件,从而实现不同的功能,同样的道理,​​CoordinatorLayout​​​可以使用​​Behavior​​​为子​​View​​​引入各种行为。​​Behavior​​也是**“可插拔”**

二.CoordinatorLayout四个功能的原理

1.CoordinatorLayout下依赖交互功能原理(观察者模式)

当​​CoordainatorLayout​​​中子控件​​depandency​​​的位置、大小等发生改变的时候,那么在​​CoordainatorLayout​​​内部会通知所有依赖​​depandency​​的控件,并调用对应声明的Behavior​​,告知其依赖的​​depandency​​发生改变。

  • 那么如何判断依赖是哪个View呢?
    ​​​layoutDependsOn​​方法
  • 接受到通知后如何处理呢?
    ​​​onDependentViewChanged​​​/​​onDependentViewRemoved​​方法

原理图

Material Design之CoordinatorLayout原理剖析_赋值_02


​dependency​​​也是一个​​child​​​,和​​child1​​​、​​child2​​​在布局上可以是并列的。后面我们把​​dependency​​​统一称为​​DepandedView​

2.CoordinatorLayout内部嵌套滑动原理

​CoordinatorLayout​​​实现了​​NestedScrollingParent2​​​接口。所以当事件(​​scroll​​​或​​fling​​​)产生后,内部实现了​​NestedScrollingChild​​​接口的子控件会将事件传递给​​CoordinatorLayout​​​,​​CoordinatorLayout​​​又会将事件传递给所有的​​Behavior​​​。然后在​​Behavior​​中实现子控件的嵌套滑动。

Material Design之CoordinatorLayout原理剖析_android_03


具体流程图

Material Design之CoordinatorLayout原理剖析_赋值_04

相对于​​NestedScrolling​​机制(参与角色只有子控件和父控件),​​CoordainatorLayout​​​中的交互角色玩出了新高度,在​​CoordainatorLayout​​​下的子控件可以与多个兄弟控件进行交互。即从​​1:1​​​变成了​​1:N​

3.CoordinatorLayout子控件的测量与布局

在特殊的情况下,如子控件需要处理宽高和布局的时候,那么交由​​Behavior​​​内部的​​onMeasureChild​​​与​​onLayoutChild​​方法来进行处理

Material Design之CoordinatorLayout原理剖析_控件_05

4.CoordinatorLayout子控件的事件拦截与响应

对于事件的拦截与处理,如果子控件需要拦截并消耗事件,那么交由给​​Behavior​​​内部的​​onInterceptTouchEvent​​​与​​onTouchEvent​​方法进行处理

Material Design之CoordinatorLayout原理剖析_嵌套_06

三.源码分析CoordinatorLayout子控件依赖交互功能原理

由于​​View​​​的生命周期的开始是在​​onAttachedToWindow​​方法中,所以我们进入此方法寻找

1.我们在CoordinatorLayout类中找到onAttachedToWindow方法

发现它调用​​getViewTreeObserver​​​,获得​​ViewTreeObserver​​​,然后调用了​​addOnPreDrawListener​

Material Design之CoordinatorLayout原理剖析_控件_07




  • 关于​​ViewTreeObserver​​​ :
    ​​​ViewTreeObserver​​​ 注册一个观察者来监听视图树,当视图树的布局、视图树的焦点、视图树将要绘制、视图树滚动等发生改变时,​​ViewTreeObserver​​​都会收到通知,​​ViewTreeObserver​​​不能被实例化,可以调用View.​​getViewTreeObserver​​()来获得
  • 关于​​dispatchOnPreDraw​​​:通知观察者绘制即将开始,如果其中的某个观察者返回​​true​​​,那么绘制将会取消,并且重新安排绘制,如果想在​​View Layout​​​ 或​​Viewhierarchy​​​ 还未依附到​​Window​​​时,或者在​​View​​​处于​​GONE​​状态时强制绘制,可以手动调用这个方法

2.接下来我们看一下它添加的监听者是个啥,追踪addOnPreDrawListener,找到OnPreDrawListener类Error message

当​​View​​​发生变化的时候会调用​​onChildViewsChanged​​方法。

3.我们追踪onChildViewsChanged

它有一个类型,即​​type​

Material Design之CoordinatorLayout原理剖析_android_08

也就是​​DispatchChangeEvent​​注解。我们点进去看一下

Material Design之CoordinatorLayout原理剖析_赋值_09

一个代表绘制之前,一个代表嵌套滑动,一个代表​​View​​​移除。意思是说这三种类型的事件发生的时候会调用​​onChildViewsChanged​​方法。

我们深究一下这个方法

final void onChildViewsChanged(@DispatchChangeEvent final int type) {
。。。
//--------------------------------------
//--------------------------------------
//得到子View的数目
//--------------------------------------
//--------------------------------------
final int childCount = mDependencySortedChildren.size();
。。。
//--------------------------------------
//--------------------------------------
//取出每一个子View,即child
//--------------------------------------
//--------------------------------------
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
// Do not try to update GONE child views in pre draw updates.
continue;
}
。。。
//--------------------------------------
//--------------------------------------
//然后再嵌套一个for循环,再依次重新取出子View
//--------------------------------------
//--------------------------------------
for (int j = i + 1; j < childCount; j++) {
//--------------------------------------
//--------------------------------------
//首先得到子View即checkChild ,然后得到子View的LayoutParams,再根据它得到Behavior
//--------------------------------------
//--------------------------------------
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
final Behavior b = checkLp.getBehavior();
//--------------------------------------
//--------------------------------------
//判断一下此时得到的checkChild是不是要依赖child
//换句话说,看一下这个child是不是被依赖的
//--------------------------------------
//--------------------------------------
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
//--------------------------------------
//--------------------------------------
//如果type是EVENT_PRE_DRAW(这个type上面说过),则调用resetChangedAfterNestedScroll
//--------------------------------------
//--------------------------------------
checkLp.resetChangedAfterNestedScroll();
continue;
}

final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
//--------------------------------------
//--------------------------------------
//如果type是EVENT_VIEW_REMOVED,则调用onDependentViewRemoved
//--------------------------------------
//--------------------------------------
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
//--------------------------------------
//--------------------------------------
//如果type是其他类型,则调用onDependentViewChanged
//--------------------------------------
//--------------------------------------
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}

if (type == EVENT_NESTED_SCROLL) {
//--------------------------------------
//--------------------------------------
//如果type是EVENT_NESTED_SCROLL,则调用setChangedAfterNestedScroll
//--------------------------------------
//--------------------------------------
checkLp.setChangedAfterNestedScroll(handled);
}
}
}
}
...
}

通过上面的源码,我们了解了在需要依赖其他控件的控件中设置一个​​behavior​​​,那么被依赖的控件也就是​​DepandedView​​​发生变化的时候就能通知对方的原因,主要是​​onChildViewsChanged​​方法。


  • 那么为什么可以调用remove相应的方法(即onDependentViewRemoved)呢?
    ①我们在​​​CoordinatorLayout​​​的构造方法中发现它调用了​​setOnHierarchyChangeListener​

  • 我们看它传入的参数是

Material Design之CoordinatorLayout原理剖析_控件_10



  • ②追踪进入,发现​​HierarchyChangeListener​​​类实现了​​OnHierarchyChangeListener​​接口。

Material Design之CoordinatorLayout原理剖析_android_11




  • ③我们再追踪​​OnHierarchyChangeListener​

Material Design之CoordinatorLayout原理剖析_赋值_12



  • 发现它是一个接口,定义了两个方法。
    当​​​EVENT_VIEW_REMOVED​​​类型事件发生的时候,就会调用​​onChildViewsChanged​​​方法,然后参数传入​​EVENT_VIEW_REMOVED​​​,然后就会回调​​Behavior​​​的​​onDependentViewRemoved​​,也就是上面源码分析的那部分



Material Design之CoordinatorLayout原理剖析_控件_13


四.源码总结

总结一波:

当​​CoordinatorLayout​​​的某一​​DepandedView​​发生变化的时候,必然会导致重绘,然后就会调用

​onChildViewsChanged​​方法,如图

Material Design之CoordinatorLayout原理剖析_android_14

在这个方法里面根据​​type​​​,再去调用​​Behavior​​的相应方法。

  • 这也是把​​OnPreDrawListener​​​作为监听者的原因。因为有些时候​​onMeasure​​​或者​​onLayout​​​可能不会调用,但是关于​​draw​​​的方法是肯定会调用的。这样把​​onChildViewsChanged​​​方法放在监听者的​​onPreDraw​​​方法中,就可以在​​DepandedView​​​发生变化的时候,及时调用​​onChildViewsChanged​​​,通知其他依赖​​DepandedView​​​的子​​View​​​们发生相应的变化。当然这些变化的方法是由​​Behavior​​调用的

一个问题:mDependencySortedChildren是啥

ok,让我们思考一个问题。不知道大家发现没有,在​​onChildViewsChanged​​​源码中,​​CoordinatorLayout​​​得到子​​View​​​的时候,不是调用的我们之前学过的​​getChildAt​​​方法,而是调用的​​mDependencySortedChildren​​的相应方法。

Material Design之CoordinatorLayout原理剖析_嵌套_15

那么​​mDependencySortedChildren​​是个啥?我们点进去看看

Material Design之CoordinatorLayout原理剖析_嵌套_16


我们看到了它下面有一个​​DirectedAcyclicGraph<View>​​类型的数据。

  • 我先解释下原因吧。在这里,因为​​CoordinatorLayout​​​管理的不仅仅是子​​View​​​,还有子​​View​​之间的关系(依赖关系)。也就是说不能用一个简单的集合,而是用这个数据结构,而​​DirectedAcyclicGraph​​就是一个图(具体来说是有向无环图,用邻接表来实现的),表示1对多的关系。当我们找到一个​​View​​​,以及其与其他​​View​​​之间的依赖关系时,就把这个​​View​​和相应依赖关系(图中叫做)存入这个​​DirectedAcyclicGraph​​​中,然后再将其某种形式存入​​mDependencySortedChildren​​​中。这就是​​mDependencySortedChildren​

第二个问题,mDependencySortedChildren如何赋值的

​mDependencySortedChildren​​​的赋值和​​DirectedAcyclicGraph​​​的赋值有很大关系,我们先看​​DirectedAcyclicGraph​

在onMeasure方法中

Material Design之CoordinatorLayout原理剖析_控件_17

我们发现了prepareChildren,追踪进入

这里面就是具体赋值的流程,都写在注释中了

private void prepareChildren() {
//--------------------------------------
//--------------------------------------
//首先对两者进行清空操作
//--------------------------------------
//--------------------------------------
mDependencySortedChildren.clear();
mChildDag.clear();

for (int i = 0, count = getChildCount(); i < count; i++) {
//--------------------------------------
//--------------------------------------
//然后得到子View,即view
//--------------------------------------
//--------------------------------------
final View view = getChildAt(i);

final LayoutParams lp = getResolvedLayoutParams(view);
lp.findAnchorView(this, view);
//--------------------------------------
//--------------------------------------
//把这个子view加入图中,作为图的节点
//--------------------------------------
//------------------------------------
mChildDag.addNode(view);

for (int j = 0; j < count; j++) {
if (j == i) {
continue;
}
//--------------------------------------
//--------------------------------------
//然后再嵌套for循环,得到子View,即other
//--------------------------------------
//------------------------------------
final View other = getChildAt(j);
if (lp.dependsOn(this, view, other)) {
if (!mChildDag.contains(other)) {
// Make sure that the other node is added
mChildDag.addNode(other);
}
//--------------------------------------
//--------------------------------------
//如果view依赖other,或者说other是DepandedView时
//就画一条从other到view的边,并加入图中,表明view和other存在依赖关系
//--------------------------------------
//------------------------------------
mChildDag.addEdge(other, view);
}
}
}

//--------------------------------------
//--------------------------------------
//调用addAll方法,将图的getSortedList加入到mDependencySortedChildren中
//--------------------------------------
//------------------------------------
mDependencySortedChildren.addAll(mChildDag.getSortedList());
//--------------------------------------
//--------------------------------------
//这里做了一个反转,无需理解,知道就行
//--------------------------------------
//------------------------------------
Collections.reverse(mDependencySortedChildren);
}

到这我们知道了​​CoordinatorLayout​​是通过图里面的边来区分谁依赖于谁的了

五.图示CoordinatorLayout下的事件传递机制

Material Design之CoordinatorLayout原理剖析_控件_18

Material Design之CoordinatorLayout原理剖析_控件_19