官网描述为: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 constructors = sConstructors.get();
if (constructors == null) {
constructors &#61; new HashMap<>();
sConstructors.set(constructors);
}
Constructor c &#61; constructors.get(fullName);
if (c &#61;&#61; null) {
final Class clazz &#61; (Class) Class.forName(fullName, true,
context.getClassLoader());
c &#61; 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 " &#43; fullName, e);
}
}
通过这一段我们可以知道&#xff0c;最后是通过调用Behavior的参数为(context,attrs)的构造函数进行实例化。
实例化出Behavior之后我们会调用behavior的onAttachedToLayoutParams方法 将LayoutParams的实例对象传进去mBehavior.onAttachedToLayoutParams(this);
mBehavior.onAttachedToLayoutParams是一个当LayoutParams被实例化后的回调方法。
通过这里&#xff0c;我们的
CoordinatorLayout就能够跟用layout_behavior标识的子View产生联系。
当子View发生变化时&#xff0c;CoordinatorLayout又是如何处理的的&#xff0c;请看下面代码&#xff1a;
final void onChildViewsChanged(&#64;DispatchChangeEvent final int type) {
final int layoutDirection &#61; ViewCompat.getLayoutDirection(this);
final int childCount &#61; mDependencySortedChildren.size();
final Rect inset &#61; acquireTempRect();
final Rect drawRect &#61; acquireTempRect();
final Rect lastDrawRect &#61; acquireTempRect();
for (int i &#61; 0; i final View child &#61; mDependencySortedChildren.get(i); final LayoutParams lp &#61; (LayoutParams) child.getLayoutParams(); if (type &#61;&#61; EVENT_PRE_DRAW && child.getVisibility() &#61;&#61; View.GONE) { // Do not try to update GONE child views in pre draw updates. continue; } // Check child views before for anchor for (int j &#61; 0; j final View checkChild &#61; mDependencySortedChildren.get(j); if (lp.mAnchorDirectChild &#61;&#61; checkChild) { offsetChildToAnchor(child, layoutDirection); } } // Get the current draw rect of the view getChildRect(child, true, drawRect); // Accumulate inset sizes if (lp.insetEdge !&#61; Gravity.NO_GRAVITY && !drawRect.isEmpty()) { final int absInsetEdge &#61; GravityCompat.getAbsoluteGravity( lp.insetEdge, layoutDirection); switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: inset.top &#61; Math.max(inset.top, drawRect.bottom); break; case Gravity.BOTTOM: inset.bottom &#61; Math.max(inset.bottom, getHeight() - drawRect.top); break; } switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: inset.left &#61; Math.max(inset.left, drawRect.right); break; case Gravity.RIGHT: inset.right &#61; Math.max(inset.right, getWidth() - drawRect.left); break; } } // Dodge inset edges if necessary if (lp.dodgeInsetEdges !&#61; Gravity.NO_GRAVITY && child.getVisibility() &#61;&#61; View.VISIBLE) { offsetChildByInset(child, inset, layoutDirection); } if (type &#61;&#61; 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 &#61; i &#43; 1; j final View checkChild &#61; mDependencySortedChildren.get(j); final LayoutParams checkLp &#61; (LayoutParams) checkChild.getLayoutParams(); final Behavior b &#61; checkLp.getBehavior(); if (b !&#61; null && b.layoutDependsOn(this, checkChild, child)) { if (type &#61;&#61; 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 &#61; true; break; default: // Otherwise we dispatch onDependentViewChanged() handled &#61; b.onDependentViewChanged(this, checkChild, child); break; } if (type &#61;&#61; 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); } 我们可以看到文档说明&#xff0c;大概意思是当子view发生变化会调用该方法。该方法会遍历所有的子view&#xff0c; 然后调用如下代码&#xff0c;layoutDependsOn()这个方法是做什么的呢&#xff1f;我们接下来看下该方法。 if (b !&#61; null && b.layoutDependsOn(this, checkChild, child)) { if (type &#61;&#61; 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 &#61; true; break; default: // Otherwise we dispatch onDependentViewChanged() handled &#61; b.onDependentViewChanged(this, checkChild, child); break; } if (type &#61;&#61; 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 {&#64;link #onDependentViewChanged} when the dependency view&#39;s layout or * position changes. * * * &#64;param parent the parent view of the given child * &#64;param child the child view to test * &#64;param dependency the proposed dependency of child * &#64;return true if child&#39;s layout depends on the proposed dependency&#39;s layout, * false otherwise * * &#64;see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View) */ public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; } 这个方法&#xff0c;大概意思是如果我们返回true&#xff0c;说明当前发生变化的子view发生变化时。也就是该方法决定我们用 layout_behavior标识的view是否应该做出相应的变化。默认返回false&#xff0c;该方法需要我们在创建自己的Behavior时重写。 当返回true的话&#xff0c;可以看到会调用 b.onDependentViewRemoved(this, checkChild, child); handled &#61; b.onDependentViewChanged(this, checkChild, child); 为了更好理解这两句代码&#xff0c;我们举个例子&#xff0c;假设有ViewA和ViewB &#xff0c;当ViewB发生移动时&#xff0c;ViewA要向反方向移动。 1、当ViewB被移除时会调用 b.onDependentViewRemoved(this, checkChild, child); 2、当ViewB发生变化时&#xff0c;会调用 handled &#61; b.onDependentViewChanged(this, checkChild, child); 那么我们就可以在 b.onDependentViewChanged里面写我们的功能代码了。 通过以上的分析&#xff0c;希望能帮到大家对CoordinatorLayout的协作调用过程有一些些的帮助。