有关于CoordinatorLayout介绍

xiaoxiao2021-02-27  381

   CoordinatorLayout作为协调一个或多个子控件的根布局,子控件使用Behavior来和父控件或其他子控件进行交互。

   CoordinatorLayout的使用核心就是Behavior,使用Behavior来执行交互。

   

  Behavior介绍:

  CoordinatorLayout.Behavior<T>,T是指这个Child,而不是dependency。Child是指这个CoordinatorLayout的子控件,dependency是指这个Child所依赖的View,即Child依赖于dependency的变化。

     这是一个观察者模式的运用,Child向CoordinatorLayout注册一个回调,告知CoordinatorLayout在dependency发生变化时通知它,这样当dependency发生变化时,Child会得到这个通知。

     其中Behavior属于子控件的属性,是CoordinatorLayout.Params的一个变量。

  添加Behavior的两种方式:

  1. 布局中使用app:layout_behavior属性

<View android:id="@+id/child" android:layout_width="150dp" android:layout_height="150dp" app:layout_behavior="xxx" />

原理是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); }

2. 添加注解

@CoordinatorLayout.DefaultBehavior(ABehavior.class) public class AView extends View { //... }

  原理是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; }

Behavior使用

// 关联View的动作,添加依赖、被观察者,主要方法 public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency){} /* 下面都是一些被观察者发生改变,观察者接收到的回调方法 */ public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency){} public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency){} // 嵌套滚动的方法,同NestedScrollingParent中方法 public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes){} public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes){} public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target){} public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed){} public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed){} public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,float velocityX, float velocityY, boolean consumed){} public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,float velocityX, float velocityY){} // 一些供子View控制的方法,measure和touch事件进行拦截处理 public boolean onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed){} public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection){} public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev){} public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev){} //...其他方法 // 其中的各种回调都是从CoordinatorLayout中进行分发,比较细节,就不赘述了

关于使用请看:CoordinatorLayout自定义Behavior的运用

接下来从CoordinatorLayout加载Child开始理解。

CoordinatorLayout依赖管理机制

这块来理解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 NestedScrollingChildHelper

NestedScrollingChild将所有的接口方法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并不难,由简到难手把手带你撸三款

转载请注明原文地址: https://www.6miu.com/read-1899.html

最新回复(0)