Android开发之CoordinatorLayout使用详解一

时间:2021-05-31 06:30:29

主页:http://cherylgood.cn/c/Android开发之CoordinatorLayout使用详解一.php

官网描述为:CoordinatorLayout是一个增强版的FrameLayout(继承自ViewGroup)

用途:

1、作为应用的顶层视图。

2、作为一个可以指定子View之间相互作用的容器,通过给CoordinatorLayout的子View指定CoordinatorLayout.Behavior 来定义子view之间的相互作用。(你可以想象成:CoordinatorLayout相当于在两个View之间充当中介,这样子的好处就是两个view之间的耦合度降低了,只需要跟coordinatorLayout打交到即可,而CoordinatorLayout.Behavior 相当于两个view之间的协议,即通过怎样的规则来约束双方的行为。)

设计概念:

  1. CoordinatorLayout:CoordinatorLayout 作为最顶层视图,将负责管理所有的子view,使其内部的子View彼此间产生一种联系。这个联系通过Behavior来实现(包括了滑动状态的处理以及View状态的处理)。
  2. AppBarLayout:AppBarLayout 继承自限性布局,作为增强版的线性布局,他增加了对滑动手势的处理。
  3. Behavior:Behavior 是google新提出的,能够让你以非侵入式的方式去处理目标View和其他View的交互行为。Behavior需要设置在触发事件(比如滚动)的view上,且这个View必须是CoordinatorLayout的第一层级下的子view,否则没有效果,因为Behavior的初始化是在CoordinatorLayout的LayoutParams中通过反射完成的。
    Behavior实例化方式:1、通过app:layout_behavior声明 ;2、在你的自定义View类上添加@DefaultBehavior(MyBehavior.class);
  4. 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:

*
  1. *
  2. Always lay out this child after the dependent child is laid out, regardless * of child order.
  3. *
  4. Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.
  5. *
*
* @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的协作调用过程有一些些的帮助。


http://cherylgood.cn/c/Android开发之CoordinatorLayout使用详解二.php