Behavior与Measure、Layout
Behavior提供了以下两个方法来代理CoordinatorLayout的onMeasureChild和onLayoutChild。
// child是被Measure的子View。
// widthUsed和heightUsed是CoordinatorLayout已使用的宽高
// 返回true则Behavior代理CoordinatorLayout的子View测量,否则由CoordinatorLayout负责。默认是返回false。
public boolean onMeasureChild(CoordinatorLayout parent, V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
return false;
}
// child是被Layout的子View。
// 返回true则Behavior代理CoordinatorLayout的子View布局,否则由CoordinatorLayout负责。默认是返回false。
// 注意1:被依赖的View总是优先于依赖的View被Layout,不管View的顺序如何。
// 注意2:如果Behavior重写了onDependentViewChanged方法,那么它也该重写onLayoutChild方法。
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
return false;
}
看看Coordinator是如何回调Behavior的这两个方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 把子View根据依赖关系进行排序,被依赖的排在前面
prepareChildren();
// 注册ViewTreeObserver.OnPreDrawListener监听器
ensurePreDrawListener();
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingRight = getPaddingRight();
final int paddingBottom = getPaddingBottom();
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthPadding = paddingLeft + paddingRight;
final int heightPadding = paddingTop + paddingBottom;
int widthUsed = getSuggestedMinimumWidth();
int heightUsed = getSuggestedMinimumHeight();
int childState = 0;
final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
final int childCount = mDependencySortedChildren.size();
// 遍历每个子View进行测量
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 计算keyline使用的距离
int keylineWidthUsed = 0;
if (lp.keyline >= 0 && widthMode != MeasureSpec.UNSPECIFIED) {
final int keylinePos = getKeyline(lp.keyline);
final int keylineGravity = GravityCompat.getAbsoluteGravity(
resolveKeylineGravity(lp.gravity), layoutDirection)
& Gravity.HORIZONTAL_GRAVITY_MASK;
if ((keylineGravity == Gravity.LEFT && !isRtl)
|| (keylineGravity == Gravity.RIGHT && isRtl)) {
keylineWidthUsed = Math.max(0, widthSize - paddingRight - keylinePos);
} else if ((keylineGravity == Gravity.RIGHT && !isRtl)
|| (keylineGravity == Gravity.LEFT && isRtl)) {
keylineWidthUsed = Math.max(0, keylinePos - paddingLeft);
}
}
int childWidthMeasureSpec = widthMeasureSpec;
int childHeightMeasureSpec = heightMeasureSpec;
// 如果调用了setWindowInsets方法,计算Inset占用的宽高。
if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
+ mLastInsets.getSystemWindowInsetRight();
final int vertInsets = mLastInsets.getSystemWindowInsetTop()
+ mLastInsets.getSystemWindowInsetBottom();
// 减去Inset占用的宽高。
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
widthSize - horizInsets, widthMode);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize - vertInsets, heightMode);
}
final Behavior b = lp.getBehavior();
// 如果Behavior不为空且Behavior不处理子View的测量,才调用CoordinatorLayout的onMeasureChild方法。
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
lp.leftMargin + lp.rightMargin);
heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin);
childState = ViewCompat.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(child));
}
final int width = ViewCompat.resolveSizeAndState(widthUsed, widthMeasureSpec,
childState & ViewCompat.MEASURED_STATE_MASK);
final int height = ViewCompat.resolveSizeAndState(heightUsed, heightMeasureSpec,
childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
setMeasuredDimension(width, height);
}
void ensurePreDrawListener() {
boolean hasDependencies = false;
final int childCount = getChildCount();
// 遍历所有子View,检查是否有依赖关系
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (hasDependencies(child)) {
hasDependencies = true;
break;
}
}
// 如果有,则添加OnPreDrawListener监听器。
// 如果没有,则移除OnPreDrawListener。
if (hasDependencies != mNeedsPreDrawListener) {
if (hasDependencies) {
addPreDrawListener();
} else {
removePreDrawListener();
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior behavior = lp.getBehavior();
// 如果Behavior不为空且Behavior不处理子View的不仅,才调用CoordinatorLayout的onLayoutChild方法。
if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
onLayoutChild(child, layoutDirection);
}
}
}
public void onLayoutChild(View child, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.checkAnchorChanged()) {
// 在布局完成之前,不能改变子View的锚点。
throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
+ " measurement begins before layout is complete.");
}
if (lp.mAnchorView != null) {
// 如果有锚点,需要根据锚点位置进行布局
layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
} else if (lp.keyline >= 0) {
// 根据keyline进行布局
layoutChildWithKeyline(child, lp.keyline, layoutDirection);
} else {
layoutChild(child, layoutDirection);
}
}
// 根据锚点位置进行布局
private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect anchorRect = mTempRect1;
final Rect childRect = mTempRect2;
// 计算锚点View的位置
getDescendantRect(anchor, anchorRect);
// 根据锚点计算子View的位置
getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
}