此篇文章需要用到之前的知识点,即我之前写的这两篇博客
事件分发机制原理分析
NestedScrolling机制原理分析
一般作为应用的顶层布局,同时也作为一个管理容器,管理与子view
或者 子view
之间的交互。那么都具体管理啥呢?可以分成四个部分
以上四个功能,都建立于CoordainatorLayout
中提供的一个叫做Behavior
的**“ 插件”**之上。Behavior内部也提供了相应方法来对应这四个不同的功能。那么都有哪些方法呢?
Behavior
中呢?答案:解耦。当用的时候就集成进去,不用的时候就remove
。即可插拔。
CoordinatorLayout
比喻成Android Studio,把子View
比喻成我们的项目。我们知道,AS
可以使用Plugins
为项目引入各种插件,从而实现不同的功能,同样的道理,CoordinatorLayout
可以使用Behavior
为子View
引入各种行为。Behavior
也是**“可插拔”**当CoordainatorLayout
中子控件depandency
的位置、大小等发生改变的时候,那么在CoordainatorLayout
内部会通知所有依赖depandency
的控件,并调用对应声明的Behavior
,告知其依赖的depandency
发生改变。
View
呢?layoutDependsOn
方法onDependentViewChanged
/onDependentViewRemoved
方法原理图
dependency
也是一个child
,和child1
、child2
在布局上可以是并列的。后面我们把dependency
统一称为DepandedView
CoordinatorLayout
实现了NestedScrollingParent2
接口。所以当事件(scroll
或fling
)产生后,内部实现了NestedScrollingChild
接口的子控件会将事件传递给CoordinatorLayout
,CoordinatorLayout
又会将事件传递给所有的Behavior
。然后在Behavior
中实现子控件的嵌套滑动。
具体流程图
相对于NestedScrolling
机制(参与角色只有子控件和父控件),CoordainatorLayout
中的交互角色玩出了新高度,在CoordainatorLayout
下的子控件可以与多个兄弟控件进行交互。即从1:1
变成了1:N
在特殊的情况下,如子控件需要处理宽高和布局的时候,那么交由Behavior
内部的onMeasureChild
与onLayoutChild
方法来进行处理
对于事件的拦截与处理,如果子控件需要拦截并消耗事件,那么交由给Behavior
内部的onInterceptTouchEvent
与onTouchEvent
方法进行处理
由于View
的生命周期的开始是在onAttachedToWindow
方法中,所以我们进入此方法寻找
CoordinatorLayout
类中找到onAttachedToWindow
方法发现它调用getViewTreeObserver
,获得ViewTreeObserver
,然后调用了addOnPreDrawListener
ViewTreeObserver
:ViewTreeObserver
注册一个观察者来监听视图树,当视图树的布局、视图树的焦点、视图树将要绘制、视图树滚动等发生改变时,ViewTreeObserver
都会收到通知,ViewTreeObserver
不能被实例化,可以调用View.getViewTreeObserver
()来获得dispatchOnPreDraw
:通知观察者绘制即将开始,如果其中的某个观察者返回true
,那么绘制将会取消,并且重新安排绘制,如果想在View Layout
或 Viewhierarchy
还未依附到Window
时,或者在View
处于GONE
状态时强制绘制,可以手动调用这个方法addOnPreDrawListener
,找到OnPreDrawListener
类
当View
发生变化的时候会调用onChildViewsChanged
方法。
onChildViewsChanged
它有一个类型,即type
也就是DispatchChangeEvent
注解。我们点进去看一下
一个代表绘制之前,一个代表嵌套滑动,一个代表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
HierarchyChangeListener
类实现了OnHierarchyChangeListener
接口。OnHierarchyChangeListener
EVENT_VIEW_REMOVED
类型事件发生的时候,就会调用onChildViewsChanged
方法,然后参数传入EVENT_VIEW_REMOVED
,然后就会回调Behavior
的onDependentViewRemoved
,也就是上面源码分析的那部分总结一波:
当CoordinatorLayout
的某一DepandedView
发生变化的时候,必然会导致重绘,然后就会调用
onChildViewsChanged
方法,如图
在这个方法里面根据type
,再去调用Behavior
的相应方法。
OnPreDrawListener
作为监听者的原因。因为有些时候onMeasure
或者onLayout
可能不会调用,但是关于draw
的方法是肯定会调用的。这样把onChildViewsChanged
方法放在监听者的onPreDraw
方法中,就可以在DepandedView
发生变化的时候,及时调用onChildViewsChanged
,通知其他依赖DepandedView
的子View
们发生相应的变化。当然这些变化的方法是由Behavior
调用的mDependencySortedChildren
是啥ok,让我们思考一个问题。不知道大家发现没有,在onChildViewsChanged
源码中,CoordinatorLayout
得到子View
的时候,不是调用的我们之前学过的getChildAt
方法,而是调用的mDependencySortedChildren
的相应方法。
那么mDependencySortedChildren
是个啥?我们点进去看看
我们看到了它下面有一个DirectedAcyclicGraph
类型的数据。
CoordinatorLayout
管理的不仅仅是子View
,还有子View
之间的关系(依赖关系)。也就是说不能用一个简单的集合,而是用图这个数据结构,而DirectedAcyclicGraph
就是一个图(具体来说是有向无环图,用邻接表来实现的),表示1对多的关系。当我们找到一个View
,以及其与其他View
之间的依赖关系时,就把这个View
和相应依赖关系(图中叫做边)存入这个DirectedAcyclicGraph
中,然后再将其某种形式存入mDependencySortedChildren
中。这就是mDependencySortedChildren
mDependencySortedChildren
如何赋值的mDependencySortedChildren
的赋值和DirectedAcyclicGraph
的赋值有很大关系,我们先看DirectedAcyclicGraph
onMeasure
方法中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
是通过图里面的边来区分谁依赖于谁的了