CoordinatorLayout作为协调一个或多个子控件的根布局,子控件使用Behavior来和父控件或其他子控件进行交互。
CoordinatorLayout的使用核心就是Behavior,使用Behavior来执行交互。
CoordinatorLayout.Behavior<T>,T是指这个Child,而不是dependency。Child是指这个CoordinatorLayout的子控件,dependency是指这个Child所依赖的View,即Child依赖于dependency的变化。
这是一个观察者模式的运用,Child向CoordinatorLayout注册一个回调,告知CoordinatorLayout在dependency发生变化时通知它,这样当dependency发生变化时,Child会得到这个通知。
其中Behavior属于子控件的属性,是CoordinatorLayout.Params的一个变量。
原理是CoordinatorLayout在generateLayoutParams中创建LayoutParams时,会调用parseBehavior方法,获取该子控件的Behavior。 此种方法必须要复写双参构造器。
try { Map<String, Constructor<CoordinatorLayout.Behavior>> constructors = sConstructors.get(); if (constructors == null) { constructors = new HashMap<>(); sConstructors.set(constructors); } Constructor<CoordinatorLayout.Behavior> c = constructors.get(fullName); if (c == null) { final Class<CoordinatorLayout.Behavior> clazz = (Class<CoordinatorLayout.Behavior>) Class.forName(fullName, true, context.getClassLoader()); /* 反射通过双参构造器创建对象,所以必须要复写双参构造器 */ c = clazz.getConstructor(CONSTRUCTOR_PARAMS); c.setAccessible(true); constructors.put(fullName, c); } return c.newInstance(context, attrs); } catch (Exception e) { throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e); }原理是CoordinatorLayout在onMearsure时调用prepareChildren方法时,遍历子控件解析LayoutParams时获取该子控件的Behavior。 此种方法必须要复写无参构造器。
LayoutParams getResolvedLayoutParams(View child) { final LayoutParams result = (LayoutParams) child.getLayoutParams(); if (!result.mBehaviorResolved) { Class<?> childClass = child.getClass(); DefaultBehavior defaultBehavior = null; // 遍历获取defaultBehavior注解值,包括其父类的,所以使用注解时具有继承性Behavior while (childClass != null &&(defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) { childClass = childClass.getSuperclass(); } if (defaultBehavior != null) { //通过无惨构造器反射创建对象,所以需要复写午餐构造器 try { result.setBehavior(defaultBehavior.value().newInstance()); } catch (Exception e) { Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() + " could not be instantiated. Did you forget a default constructor?", e); } } result.mBehaviorResolved = true; } return result; }关于使用请看:CoordinatorLayout自定义Behavior的运用
接下来从CoordinatorLayout加载Child开始理解。
这块来理解CoordinatorLayout是怎么管理各个子View的。子View的依赖关系,它们是如何互相依赖的。
// 依赖关系排序后的图,根据子View的依赖关系来measure、layout、按照顺序触发回调等 private final List<View> mDependencySortedChildren = new ArrayList<>(); // 有向无环图,使用邻接表表示该图 private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>(); /* onMeasure时调用该方法,初始化该图 */ private void prepareChildren() { mDependencySortedChildren.clear(); mChildDag.clear(); // 嵌套循环寻找依赖,构建图和边。 for (int i = 0, count = getChildCount(); i < count; i++) { final View view = getChildAt(i); final CoordinatorLayout.LayoutParams lp = getResolvedLayoutParams(view); lp.findAnchorView(this, view); 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); final CoordinatorLayout.LayoutParams otherLp = getResolvedLayoutParams(other); if (otherLp.dependsOn(this, other, view)) { if (!mChildDag.contains(other)) { // Make sure that the other node is added mChildDag.addNode(other);//添加结点 } // Now add the dependency to the graph mChildDag.addEdge(view, other);//添加弧 } } } /* 获取拓扑排序后的List, 根据依赖关系调用Child的Behavior进行onMeasure和onLayout。 */ // Finally add the sorted graph list to our list 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); } boolean dependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency == mAnchorDirectChild //layout_archor || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent)) || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency)); }CoordinatorLayout里面可以放置很多的子View,并且任何一个子View可以依赖别的子View,所以源码中使用了有向图来表示这种关系。 至于是无环的,从直觉上来看,如果A依赖B,B也依赖A,则无法判断谁先绘制等一系列操作;从代码上看,在DirectedAcyclicGraph中,使用DFS进行拓扑排序时,并检查了是否有环,如果发现有互相依赖的节点,则会抛出RuntimeException。 所以必须要求子View间只能单向依赖。
private void dfs(final T node, final ArrayList<T> result, final HashSet<T> tmpMarked) { // ... if (tmpMarked.contains(node)) { throw new RuntimeException("This graph contains cyclic dependencies"); } // ... }在触发一系列滚动方法时,使用到了嵌套滑动机制。这块主要描述该机制。
/* This interface should be implemented by ViewGroup subclasses that wish to support scrolling operations delegated by a nested child view. Classes implementing this interface should create a final instance of a NestedScrollingParentHelper as a field and delegate any View or ViewGroup methods to the NestedScrollingParentHelper methods of the same signature. */ NestedScrollingParent NestedScrollingParentHelper /* This interface should be implemented by View subclasses that wish to support dispatching nested scrolling operations to a cooperating parent ViewGroup. Classes implementing this interface should create a final instance of a NestedScrollingChildHelper as a field and delegate any View methods to the NestedScrollingChildHelper methods of the same signature. */ NestedScrollingChild NestedScrollingChildHelperNestedScrollingChild将所有的接口方法delegate到NestedScrollingChildHelper中,并且在onIntercepter、onTouchEvent中调用这些方法,将事件通过接口方法dispatch给NestedScrollingParent,在NestedScrollingParent中对事件进行处理,完成嵌套滚动。 从描述可以看出,子View接收事件,通过view.getParent()方法获取父控件,在此将事件分发到父View,让父View有机会去消耗子View的事件,这种思想值得学习。
在CoordinatorLayout中,所有的Behavior都有机会拦截事件,并接收从NestedScrollingParent中的所有嵌套滚动方法。
详情请参考:
介绍 CoordinatorLayout的使用如此简单 彻底搞懂CoordinatorLayout CoordinatorLayout 子 View 之间的依赖管理机制 —— 有向无环图
自定义Behavior示例 CoordinatorLayout自定义Behavior的运用
嵌套滑动 Android NestedScrolling机制完全解析 带你玩转嵌套滑动 NestedScrolling事件机制源码解析
例子: CoordinatorBehaviorExample 一个神奇的控件——Android CoordinatorLayout与Behavior使用指南 CoordinatorLayout 自定义Behavior并不难,由简到难手把手带你撸三款