CoordinatorLayout 协调者布局

前言

CoordainatorLayout 顾名思义 协调者布局,也就是一个中间者这样的角色,核心工作就在协调上面了。所以我会从下面3个切入点分析CoordainatorLayout

  • 协调什么?
  • 协调哪些View?
  • 如何协调?

协调什么

要了解一个控件,我们要知道他是用来做什么的,这里先做个总结

  1. 处理子控件之间依赖下的交互
  2. 处理子控件之间的嵌套滑动
  3. 处理子控件的测量与布局
  4. 处理子控件的事件拦截与响应

以上四个功能,都建立于 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<>();
复制代码

CoordinatorLayoutonMeasure调用prepareChildren刷新mDependencySortedChildrenmChildDag 数据

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内部控件的测量与布局,非常简单,我们可以跟踪CoordinatorLayoutonMeasureonLayout 方法。

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

如果 onMeasureChildonLayoutChild返回:

  • true Behavior 自己内部处理
  • false 不处理,交由CoordinatorLayout处理
事件拦截与处理

同理测量与布局原理, 如果子控件需要拦截并消耗 事件,那么交由给Behavior 内部的 onInterceptTouchEventonTouchEvent方法进行处理

onInterceptTouchEventonTouchEvent返回:

  • true Behavior 自己内部处理
  • false 不处理,交由CoordinatorLayout处理
分类:
Android
标签: