测量完之后的下一步就要开始布局摆放了。这个拜访跟具体容器类型有很大关系,例如 LinearLayout 和 RelativeLayout 的布局规则完全不一样,所以要结合具体容器考虑。
先从 ViewRootImpl 的 performLayout() 方法看起,
//ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
//依然从 DecorView 开始
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//接下去的像是在布局过程中的二次执行工作流程,暂时先不考虑
//先继续看 DecorView 的 layout
}
DecorView 的 layout 过程估计和 measure 过程差不多,也是先调用父类 View 的方法 layout(),然后再调用自己和父类 FrameLayout 的方法,
//先看 View 的 layout
public void layout(int l, int t, int r, int b) {
//把旧的位置信息缓存起来,到时候给回调响应用
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//判断是否需要布局调整
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//和 onMeasure 很相似
onLayout(changed, l, t, r, b);
//这里是看看有没有布局更新的监听器,有的话,要做监听回调
//可见如果 View 实现了 OnLayoutChangeListener 就会在这个时候回调
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList listenersCopy = (ArrayList)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
}
//DecorView 的 onLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//下面这些代码我理解的是,经过 FrameLayout 的 onLayout 之后
//布局位置基本已经定了,只是再修正一些有效区域,所以重点在 super.onLayout 上
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);
}
updateElevation();
mAllowUpdateElevation = true;
if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
FrameLayout 的 onLayout() 方法估摸着也会涉及到遍历子 View,
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
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;
//简单看就是对于不是 GONE 的 View 会根据尺寸和左上角坐标进行布局
//这里的核心工作就是确定左上角坐标点,
//View 的尺寸已经在测量的时候测量好了
//这里也知道了一点,对于 GONE 和 INVISIBLE 的效果,虽说有可能看去一样,但代码执行上是不一样的,所以能 GONE 的地方尽量 GONE
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);
}
}
}
经过上面过程的分析,可见容器类的 View 的 layout 步骤是必须的,并且其实系统提供的一些控件,例如 TextView(找了一些常用控件,发现基本上父类都是 TextView ......)也自己实现了 onLayout() 方法,不过也有没实现的,例如 ImageView。看来对具体控件来说 onLayout 的实现可以不需要。