前言
CoordainatorLayout 顾名思义 协调者布局,也就是一个中间者这样的角色,核心工作就在协调上面了。所以我会从下面3个切入点分析CoordainatorLayout
- 协调什么?
- 协调哪些View?
- 如何协调?
协调什么
要了解一个控件,我们要知道他是用来做什么的,这里先做个总结
- 处理子控件之间依赖下的交互
- 处理子控件之间的嵌套滑动
- 处理子控件的测量与布局
- 处理子控件的事件拦截与响应
以上四个功能,都建立于 CoordainatorLayout中提供 的一个叫做Behavior的 “插件”之上。Behavior 内部也提供了相应方法来对 应这四个不同的功能
我整理了一张图整理下介绍他们方法 对应的功能
协调哪些View
CoordinatorLayout有两个成员变量
//存放 CoordinatorLayout下 经过依赖排序的View
private final List<View> mDependencySortedChildren = new ArrayList<>();
//功能 1.存放 依赖表 1:n 2.生成排序列表
private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
复制代码
CoordinatorLayout中onMeasure
调用prepareChildren
刷新mDependencySortedChildren 和mChildDag 数据
private void prepareChildren() {
mDependencySortedChildren.clear();
mChildDag.clear();
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(view);
lp.findAnchorView(this, view);
//1.按照CoordinatorLayout子View的顺序添加节点。2.addNode 方法里面会判断View是否已添加,保证View的依赖顺序。3.打个比方: CoordinatorLayout 布局XMl里面有按顺序存放的 A 、B、C 3个子View, 但是A 被C依赖,B没有被依赖,那么最终mDependencySortedChildren 列表的顺序就是 A 、C、B
mChildDag.addNode(view);
// Now iterate again over the other children, adding any dependencies to the graph
for (int j = 0; j < count; j++) {
if (j == i) {
continue;
}
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);
}
// Now add the dependency to the graph
mChildDag.addEdge(other, view);
}
}
}
// 4.最终获取有依赖顺序的CoordinatorLayout子View
mDependencySortedChildren.addAll(mChildDag.getSortedList());
// We also need to reverse the result since we want the start of the list to contain
// Views which have no dependencies, then dependent views after that
Collections.reverse(mDependencySortedChildren);
}
复制代码
小结
CoordinatorLayout协调他的直接子View,并在onMeasure
的时候会将coordinatorLayout的一级子View进行依赖排序,以及依赖键值列表。
如何协调
我们上面讲过CoordinatorLayout主要协调的4个功能,这里我们对4个功能原理进行展开分析
CoordinatorLayout 事件
CoordinatorLayout 类里面有一个onChildViewsChanged
final 方法负责下面列出来的3个事件
//预绘制
static final int EVENT_PRE_DRAW = 0;
//嵌套滑动事件
static final int EVENT_NESTED_SCROLL = 1;
//view 被移除事件
static final int EVENT_VIEW_REMOVED = 2;
/** @hide */
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(RetentionPolicy.SOURCE)
@IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED})
public @interface DispatchChangeEvent {}
复制代码
事件的来源:
- 在构造方法里面会有
setOnHierarchyChangeListener
方法里会监听View 的 add 和 remove 触发EVENT_VIEW_REMOVED - 在
onAttachedToWindow
里面通过调用addOnPreDrawListener
监听预绘制事件 - CoordinatorLayout 类实现 NestedScrollingParent 接口,所以嵌套滑动通过Nested Scroll 机制回调
依赖交互原理
当CoordainatorLayout中子控件depandency的位置、大小等发生改 变的时候, 那么在CoordainatorLayout内部会 通知所有依赖depandency的控件, 并调用对应声明的Behavior,告知 其依赖的depandency发生改变。那 么如何判断依赖 (layoutDependsOn),接受到通知 后如何处理 (onDependentViewChanged/onDepe ndentViewRemoved),这些都交由 Behavior来处理。
嵌套滑动原理
CoordinatorLayout实现了 NestedScrollingParent接口。 那么当事件(scroll或fling) 产生后,内部实现了 NestedScrollingChild接口的 子控件会将事件分发给 CoordinatorLayout, CoordinatorLayout又会将事件 传递给所有的Behavior。然后 在Behavior中实现子控件的嵌 套滑动。
测量与布局原理
CoordinatorLayout内部控件的测量与布局,非常简单,我们可以跟踪CoordinatorLayout的 onMeasure
和 onLayout
方法。
在特殊的情况下, 如子控件需要处理宽高和布局的时候, 那么交由Behavior内部的 onMeasureChild
与onLayoutChild
方法 来进行处理。
如果 onMeasureChild
与onLayoutChild
返回:
- true Behavior 自己内部处理
- false 不处理,交由CoordinatorLayout处理
事件拦截与处理
同理测量与布局原理, 如果子控件需要拦截并消耗 事件,那么交由给Behavior 内部的 onInterceptTouchEvent
与 onTouchEvent
方法进行处理
onInterceptTouchEvent
与 onTouchEvent
返回:
- true Behavior 自己内部处理
- false 不处理,交由CoordinatorLayout处理