layout作为View绘制的三个过程中的第二个过程,负责在measure过程完成之后确定每个View的位置,也是在这个阶段,View的最终宽高才能真正确定(measure过程计算的是测量宽高)。如果你还不清楚measure过程是如何进行的,可以浏览【Android系列】View的绘制之measure过程。
layout过程和measure过程类似,也是从DecorView开始,并经由父View传递到子View,最终在View树的叶子节点处结束。在layout过程中,通过layout
方法可以确定View自身的位置,通过onLayout
方法可以确定子View在父View中的位置。
layout过程是由performLayout
方法执行的。在performLayout
方法中有这样一段代码:
/* ViewRootImpl.performLayout */
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
从这里可以看出,layout过程同样是从DecorView开始的。
在文章一开头就提到过:“在layout过程中,通过layout
方法可以确定View自身的位置,通过onLayout
方法可以确定子View在父View中的位置”。也就是说,layout过程是在父View确定子View位置的时候传递到子View的。
layout
方法负责确定View自身的位置,和measure
方法类似,在layout
方法的注释中有这样一段话:
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.
因此,在自定义ViewGroup时不需要重写layout
方法,而只要也必须重写onLayout
方法。下面我们还是来看看layout
方法的源码:
/* 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) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
}
}
首先,layout
方法的四个参数分别代表当前View的四个顶点的位置;接着,通过setFrame
方法设置自身的位置(setOpticalFrame
方法内部调用了setFrame
方法);最后,又调用了onLayout
方法设置子View的位置。
在setFrame
方法中:
/* View.setFrame */
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
可以看到,这里设置了mLeft
、mTop
、mRight
和mBottom
四个成员变量的值,而它们正好表示View的边界,这样就设置了View的位置。
就像我们不能在ViewGroup中找到onMeasure
方法一样,在ViewGroup中同样“没有”onLayout
方法。实际上,ViewGroup的onLayout
方法是一个抽象方法(不同View容器的布局规则不同),View的onLayout
方法是一个空方法(View没有子View)。通过layout
方法的注释我们知道,每个ViewGroup都要重写onLayout
方法。这里我们仍以FrameLayout为例,看看它的onLayout
方法做了些什么。由于FrameLayout的onLayout
方法直接调用了layoutChildren
方法,因此我们直接看它的源码:
/* FrameLayout.layoutChildren */
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;
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;
}
// vertical cases
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
在FrameLayout的onLayout
方法中,是通过子View的gravity来确定其位置的,换句话说,只要确定了子View的gravity,就确定了子View在FrameLayout中的位置。那么子View的gravity是如何确定的呢?
首先,通过getLayoutDirection
方法确定当前布局的水平方向是从左到右还是从右到左;接着,通过getAbsoluteGravity
方法把相对gravity转换成绝对gravity,即把start和end转换成left和right;最后,通过掩码得到水平方向的gravity和垂直方向的gravity。
在得到子View的gravity之后,会根据其计算子View的左边界和上边界位置,即左上方顶点的位置。在得到子View的左上方顶点的位置后,由于子View的测量宽高已经确定了,因此又调用了子View的layout
方法确定子View的位置,layout过程也在此进入到了子View中。
就像上面提到的,View的onLayout
方法是一个空方法,因此,layout过程到这里也结束了。
和measure过程比起来,layout过程简单了很多,如果你已经清楚了measure过程,相信layout过程也难不倒你,最后,同样给出一个layout过程的流程图:
扫一扫,关注我٩(๑>◡<๑)۶