Android - View的绘制流程二(layout)
在Android - View的绘制流程一(measure)一文中提到,view绘制的核心逻辑都在ViewRoot的performTraversals()方法中,主要分为三个阶段: 第一个阶段是measure,第二个阶段是layout,第三个阶段是draw
ViewRoot类的performTraversals方法中layout方法的调用,代码如下:
private void performTraversals()
measure过程结束后,从这里开始layout,同样host是一个DecorView对象。host.mMeasuredWidth 和 host.mMeasuredHeight 则是 measure过程结束之后得到的host的宽和高。
在较低的版本中,定义在view中的layout方法是被final修饰的,不能被复写,本博文参考的是android-19代码,layout没有被final修饰,不过不影响大致流程的理解:
public void
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);
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;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
return changed;
}
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
}
}
layout方法的参数由父类提供,参数指定了该子视图在父视图中的左、上、右、下的位置,主要逻辑如下:
1、调用setFrame方法,先比对这些参数是否和原来的相同,如果相同,则什么都不做,只要四个参数中有一个发生改变,则将这些参数赋值给view的成员变量(mLeft、mTop、mRight和mBottom)。
2、调用onLayout方法。
// View 中的onLayout方法的定义:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
// ViewGroup 中的onLayout方法的定义:
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
View 中的onLayout方法默认什么都不做,ViewGroup中的onLayout方法则是一个抽象方法,所以,ViewGroup类型的视图都会在自己类中对子视图进行位置计算。
和Android - View的绘制流程一(measure)一样,也从MyCustomLinearLayoutA为例,从onLayout()方法开始分析一个ViewGroup类型的视图遍历—layout子视图的详细过程。
LinearLayout类中的onLayout()方法:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
根据LinearLayout的布局方向调用layoutVertical或layoutHorizontal,下面以Android - View的绘制流程一(measure)demo中MyCustomLinearLayoutA为例分析竖直方向的layoutVertical。
LinearLayout的layoutVertical方法:
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
// LinearLayout可用的宽度
final int width = right - left;
// child最多能够到达的右边位置
int childRight = width - mPaddingRight;
// Space available for child
// 计算child的实际可用空间
int childSpace = width - paddingLeft - mPaddingRight;
// 计算子view的个数
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// 首先根据gravity的值计算出child的top值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
// 然后进行遍历,根据child的水平布局方式(水平居中、靠左或靠右)计算出child的left值
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// child的宽度和高度已经在measure步骤中计算过了,现在直接获取
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
// 获取child的布局参数
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
// 计算完child的top和left值,获取到measure步骤中计算的宽和长,再调用setChildFrame方法设置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
private void setChildFrame(View child, int left, int top, int width, int height) {
// setChildFrame方法中调用的就是layout方法
child.layout(left, top, left + width, top + height);
}
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
至此,view的layout过程就分析完成了。
layout过程的核心就是根据xml文件中的gravity属性计算出top和left的值,再利用第一步 — measure所得到的宽和高的值计算出view对象的坐标(left、top、right和bottom)。
大小和位置都确定了,接下来是第三步 — draw。