官网描述为:CoordinatorLayout 是一个增强版的 FrameLayout(继承自 ViewGroup)
用途:
1、作为应用的顶层视图。
2、作为一个可以指定子 View 之间相互作用的容器,通过给 CoordinatorLayout 的子 View 指定 CoordinatorLayout.Behavior 来定义子 view 之间的相互作用。(你可以想象成:CoordinatorLayout 相当于在两个 View 之间充当中介,这样子的好处就是两个 view 之间的耦合度降低了,只需要跟 coordinatorLayout 打交到即可,而 CoordinatorLayout.Behavior 相当于两个 view 之间的协议,即通过怎样的规则来约束双方的行为。)
设计概念:
- CoordinatorLayout:CoordinatorLayout 作为最顶层视图,将负责管理所有的子 view,使其内部的子 View 彼此间产生一种联系。这个联系通过 Behavior 来实现(包括了滑动状态的处理以及 View 状态的处理)。
- AppBarLayout:AppBarLayout 继承自限性布局,作为增强版的线性布局,他增加了对滑动手势的处理。
- Behavior:Behavior 是 google 新提出的,能够让你以非侵入式的方式去处理目标 View 和其他 View 的交互行为。Behavior 需要设置在触发事件(比如滚动)的 view 上,且这个 View_必须是 CoordinatorLayout 的第一层级下的子 view_,否则没有效果,因为 Behavior 的初始化是在 CoordinatorLayout 的 LayoutParams 中通过反射完成的。
Behavior 实例化方式:1、通过 app:layout_behavior 声明 ;2、在你的自定义 View 类上添加 @DefaultBehavior(MyBehavior.class); - Behavior 只是个接口,其调用是由_NestedScrollingParent 与 NestedScrollingChild 接口负责调用。_
接下来我们通过阅读部分源码进行学习:
首先,我们从两个 view 是如何通过 coordinatorlayout 产生关联来入手;看代码
LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout_Layout); this.gravity = a.getInteger( R.styleable.CoordinatorLayout_Layout_android_layout_gravity, Gravity.NO_GRAVITY); mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor, View.NO_ID); this.anchorGravity = a.getInteger( R.styleable.CoordinatorLayout_Layout_layout_anchorGravity, Gravity.NO_GRAVITY); this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline, -1); insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0); dodgeInsetEdges = a.getInt( R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0); mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_Layout_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior)); } a.recycle(); if (mBehavior != null) { // If we have a Behavior, dispatch that it has been attached mBehavior.onAttachedToLayoutParams(this); } }
从
mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_Layout_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior)); }
这几句我们可以看到。
mBehaviorResolved 是个 boolean 变量,如果
R.styleable.CoordinatorLayout_Layout_layout_behavior CoordinatorLayout 的
layout_behavior 这个字段设置有值,
1、mBehaviorResolved = true -》调用 parseBehavior 方法,将所需参数传入通过 java 的反射技术返回一个 Behavior 实例。
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
// Relative to the app package. Prepend the app package name.
fullName = context.getPackageName() + name;
} else if (name.indexOf('.') >= 0) {
// Fully qualified package name.
fullName = name;
} else {
// Assume stock behavior in this package (if we have one)
fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
? (WIDGET_PACKAGE_NAME + '.' + name)
: name;
}
try {
Map<String, Constructor> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor c = constructors.get(fullName);
if (c == null) {
final Class clazz = (Class) 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);
}
}
通过这一段我们可以知道,最后是通过调用 Behavior 的参数为(context,attrs)的构造函数进行实例化。
实例化出 Behavior 之后我们会调用 behavior 的 onAttachedToLayoutParams 方法 将 LayoutParams 的实例对象传进去 mBehavior.onAttachedToLayoutParams(this);
mBehavior.onAttachedToLayoutParams 是一个当 LayoutParams 被实例化后的回调方法。
通过这里,我们的
CoordinatorLayout 就能够跟用 layout_behavior 标识的子 View 产生联系。
当子 View 发生变化时,CoordinatorLayout 又是如何处理的的,请看下面代码:
final void onChildViewsChanged(@DispatchChangeEvent final int type) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); final Rect inset = acquireTempRect(); final Rect drawRect = acquireTempRect(); final Rect lastDrawRect = acquireTempRect(); 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; } // Check child views before for anchor for (int j = 0; j < i; j++) { final View checkChild = mDependencySortedChildren.get(j); if (lp.mAnchorDirectChild == checkChild) { offsetChildToAnchor(child, layoutDirection); } } // Get the current draw rect of the view getChildRect(child, true, drawRect); // Accumulate inset sizes if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) { final int absInsetEdge = GravityCompat.getAbsoluteGravity( lp.insetEdge, layoutDirection); switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: inset.top = Math.max(inset.top, drawRect.bottom); break; case Gravity.BOTTOM: inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top); break; } switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: inset.left = Math.max(inset.left, drawRect.right); break; case Gravity.RIGHT: inset.right = Math.max(inset.right, getWidth() - drawRect.left); break; } } // Dodge inset edges if necessary if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) { offsetChildByInset(child, inset, layoutDirection); } if (type == EVENT_PRE_DRAW) { // Did it change? if not continue getLastChildRect(child, lastDrawRect); if (lastDrawRect.equals(drawRect)) { continue; } recordLastChildRect(child, drawRect); } // Update any behavior-dependent views for the change for (int j = i + 1; j < childCount; j++) { final View checkChild = mDependencySortedChildren.get(j); final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); final Behavior b = checkLp.getBehavior(); if (b != null && b.layoutDependsOn(this, checkChild, child)) { if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } } } } releaseTempRect(inset); releaseTempRect(drawRect); releaseTempRect(lastDrawRect); }
我们可以看到文档说明,大概意思是当子 view 发生变化会调用该方法。该方法会遍历所有的子 view,
然后调用如下代码,layoutDependsOn()这个方法是做什么的呢?我们接下来看下该方法。
if (b != null && b.layoutDependsOn(this, checkChild, child)) { if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } }
/** * Determine whether the supplied child view has another specific sibling view as a * layout dependency. * *
This method will be called at least once in response to a layout request. If it * returns true for a given child and dependency view pair, the parent CoordinatorLayout * will:
*
-
Always lay out this child after the dependent child is laid out, regardless * of child order.
-
Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.
* * @param parent the parent view of the given child * @param child the child view to test * @param dependency the proposed dependency of child * @return true if child's layout depends on the proposed dependency's layout, * false otherwise * * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View) */ public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; }
这个方法,大概意思是如果我们返回 true,说明当前发生变化的子 view 发生变化时。也就是该方法决定我们用
layout_behavior 标识的 view 是否应该做出相应的变化。默认返回 false,该方法需要我们在创建自己的 Behavior 时重写。
当返回 true 的话,可以看到会调用
b.onDependentViewRemoved(this, checkChild, child);
handled = b.onDependentViewChanged(this, checkChild, child);
为了更好理解这两句代码,我们举个例子,假设有 ViewA 和 ViewB ,当 ViewB 发生移动时,ViewA 要向反方向移动。
1、当 ViewB 被移除时会调用
b.onDependentViewRemoved(this, checkChild, child);
2、当 ViewB 发生变化时,会调用
handled = b.onDependentViewChanged(this, checkChild, child);
那么我们就可以在
b.onDependentViewChanged 里面写我们的功能代码了。
通过以上的分析,希望能帮到大家对 CoordinatorLayout 的协作调用过程有一些些的帮助。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于