你家房子装修,你想在一面墙上画一幅画,你告诉工人:“给我画一副长两米高一米的风景画”(measure),工人就该问你了:“画在墙上的什么位置?”,这个位置的确定,就是布局(Layout)过程完成的。
Layout 的过程实际上和 Measure 类似,也是从上到下的去执行布局操作。
在 setContentView 那篇文章中,我们讲了在 RootViewImpl 对象的 performTraversals 方法中,会执行 performXXX 开始测量、布局和绘制过程:
private void performTraversals() {
...
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
...
if (!mStopped || mReportNextDraw) {
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
}
} else {
...
}
...
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
}
...
if (!cancelDraw && !newSurface) {
...
performDraw();
} else {
...
}
mIsInTraversal = false;
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//省略好几行代码
final View host = mView;
//又省略好几行代码
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//依旧省略好几行代码
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
首先我们确定 mView 实际上是一个 DecorView,实际上是调用了 DecorView 的 layout 方法,DecorView 及其父类 FrameLayout 都没有 layout,只能看 FrameLayout 的父类 ViewGroup 的 layout 方法了.
@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;
}
}
调用了父类 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;
// 【重点】,调用 setOpticalFrame 或者 setFrame
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 根据 setOpticalFrame 或者 setFrame 的返回值,来确定是否调用onLayout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
//省略好多代码
}
//省略好多代码
}
那么 setOpticalFrame 和 setFrame 是用来干什么的呢?
setOpticalFrame 方法最终会调用 setFrame:
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
看 setFrame:
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;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
// 保存旧数据
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
// 保存新数据
int newWidth = right - left;
int newHeight = bottom - top;
//【宽高】发生变化了吗?
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// 是否请求重绘?
invalidate(sizeChanged);
//保存新数据
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
//宽高发生变化时调用
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
// 还是省略
}
return changed;
}
在 setFrame 方法中,判断我们的 View 是否发生了位置或者大小的变化,如果发生了变化,则强制性重绘。
在 layout 方法中,我们通过 setFrame 发现位置改变了,调用 onLayout 方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
我们发现该方法为空,为什么呢?
每个布局当中子元素的排序是不同的,所以根据不同的排列形式,需要重写 onLayout 方法,来放置子元素。
我们来看一下 FrameLayout 的 onLayout 方法:
@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) {
//拿到子元素的 LayoutParams
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,针对不同方向的 gravity做处理
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;
}
//拿上面switch得到的子元素的上下左右距离开始子元素的布局
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
可以看到,在处理子元素的布局时,需要考虑到 padding 值,因为子元素在父布局中的位置受到 padding 值的影响。