ViewRootImpl 类的 performTraverserals() 方法中的 performMeasure(...) 方法执行完成后,开始调用 performLayout(lp, mWidth, mHeight) 方法。
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
......
final View host = mView;
......
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
......
}
其中,lp 参数在 WindowManager.LayoutParams lp = mWindowAttributes 这里被赋值,mWidth 和 mHeight 参数分别为屏幕的宽和高。mView 也就是DecorView,layout 流程也是从 DecorView 开始遍历和递归的。
1、正式启动布局流程的 layout 方法
由于 DecorView 是 ViewGroup 子类,该方法为 final 修饰的方法,所以不可重写。所以代码逻辑实际上是先进入到 ViewGroup 类中的 layout 方法中。
// ViewGroup.java
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
然后主要还是调用 super.layout 方法,即 View 类的 layout 方法。
下面便是 View 中的 layout 方法,负责给 View 和其所有子 View 分配 size 大小和 position 位置。省略了绝大部分非主线内容。
// View.java
/**
* Assign a size and position to a view and all of its
* descendants
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
......
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
......
}
......
}
layout 方法的代码量也是不少,只看主干部分会好很多。这是绘制流程的第二个重要阶段(第一个阶段是 measure)。注释中说明,在这个阶段,每一个父布局都会调用所有子布局的 layout 方法来给它们进行定位,确认它们的 position。一般来说,这个过程会使用子布局在 measure 阶段存储的测量值参数。
一般派生类不应该重写该方法,有子 View 的派生类(也就是继承自 ViewGroup 的容器类,父布局)应该重写 onLayout 方法。在重写的 onLayout 方法中,父布局应该为每个子 View 调用 layout 方法进行布局。
该方法的参数依次为左、上、右、下四个边界相对于父布局的位置大小。
方法中通过 setFrame(l, t, r, b) 方法给 View 设置相对于父布局的位置参数(setOpticalFrame 方法最终也是调用 setFrame 方法),然后先判断 View 的位置是否发生过变化,看是否有必要进行重新 layout,如果有变化则调用调用 onLayout(changed, l, t, r, b) 方法。
下面是 setFrame 方法的部分代码。
// View.java
/**
* Assign a size and position to this view.
*
* This is called from layout.
*
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
* @return true if the new size and position are different than the
* previous ones
* {@hide}
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
......
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
......
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
......
}
return changed;
}
该方法主要用于给 View 分配 size 大小和 position 位置,也就是说,实际的布局工作是在这里完成的。如果新的 left、top、right、bottom 参数和mLeft、mTop、mRight、mBottom 有一个不同,changed 标志会被设为 true,该方法则会则返回 true(size 和 position 通过 mLeft、mTop、mRight、mBottom 获得)。
视线回到 layout 方法中,会看到如果 view 发生了改变,接下来会调用 onLayout 方法(这里是 View 中的 onLayout 方法),这和 measure 调用 onMeasure 方法类似。当该 view 要分配 size 和 position 给它的每一个子 view 时,该方法会在 layout 方法中被调用。有子 view 的派生类(也就是继承自 ViewGroup 的容器布局,父布局)应该重写该方法并且为每一个子 view 调用 layout。
View 类中的 onLayout 方法。
// View.java
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
View 类中该方法是一个空方法,毕竟 layout 过程是父布局容器布局子 view 的过程,onLayout 方法对于叶子 view 来说没有意义,只有 ViewGroup 等容器布局才有用。所以,如果当前 View 是一个容器,那么流程会切到被重写的 onLayout 方法中。
下面就来看 ViewGroup 类中的 onLayout 方法。
// ViewGroup.java
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
可以看到,这是一个抽象方法,所以 ViewGroup 的子类都必须要重写 onLayout 方法。事实上,layout 流程是绘制流程中必需的过程,而前面讲过的 measure 流程,其实可以不要,因为 measure 阶段获得的测量值在 layout 阶段被使用,而在 layout 阶段不一定需要使用 measure 的测量值,不过不建议这么做,这一点后面可以理解。
所以直接进入到 DecorView 中查看重写的 onLayout 方法。
// DecorView.java
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
getOutsets(mOutsets);
if (mOutsets.left > 0) {
offsetLeftAndRight(-mOutsets.left);
}
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
if (mApplyFloatingVerticalInsets) {
offsetTopAndBottom(mFloatingInsets.top);
}
if (mApplyFloatingHorizontalInsets) {
offsetLeftAndRight(mFloatingInsets.left);
}
// If the application changed its SystemUI metrics, we might also have to adapt
// our shadow elevation.
updateElevation();
mAllowUpdateElevation = true;
if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
主要逻辑还是调用的父类的 onLayout 方法,即 FrameLayout 的 onLayout 方法。
下面就是 FrameLayout 的 onLayout 方法。
// FrameLayout.java
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
onLayout 方法中通过调用 layoutChildren 方法遍历子 View 来实现对每一个子 View 进行布局的。最终调用了子 View 的 layout 方法。如果该子 View 是一个继承自 ViewGroup 的容器布局,那么 layout 流程会继续传递下去;如果是叶子 View,则会走到 View 的 onLayout 空方法,该叶子 View 的布局流程就完成了。
此外,在方法中可以看到,width 和 height 分别来自于 measure 阶段存储的测量值,如果在这个阶段通过其他渠道赋给了 width 和 height 值,那么 measure 阶段就不需要了。当然,肯定是不建议这么做的,采用其它方式很难实现我们要的效果果。
2、View 的 layout 过程的简易流程图
