Layout布局流程
Layout作用是ViewGroup用来确定子元素的位置的。如果当前是一个View,则会通过layout方法确定当前View的位置;如果当前是一个ViewGroup,除了在layout方法中确定当前ViewGroup的位置外,还会调用onLayout方法分别确定它child的位置,如果child中有ViewGroup,还会继续执行该过程,直到完成该View树的layout。
View的布局
View的layout方法
@SuppressWarnings({"unchecked"})
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);
//...
}
//...
}
首先用局部变量记录了当前View的左上右下的位置,然后调用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 our old position
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;
}
将新传入的左上右下的值和成员变量的值对比,如果有一个不一样就证明当前布局发生了变化,changed置为true,changed作为方法的返回值返回。计算新的宽高和原来的宽高比较,如果有不同就证明View的尺寸也发生了改变,将新的位置赋值给成员变量,并且回调sizeChange方法,这样View的位置也就确定了。
ViewGroup的布局
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方法,根据上面的分析,先确定当前ViewGroup的位置。然后就调用了onLayout方法进行子child的布局,由于各种ViewGroup的布局特性也不同,所以ViewGroup并没有实现onLayout方法,而是由各ViewGroup子类实现具体的布局方法。以LinearLayout为例,其onLayout如下
@Override
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);
}
}
与Measure测量时一样,也是分布局方向进行处理,垂直方向的布局调用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
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
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;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
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;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
根据LinearLayout垂直方向的Gravity计算出child垂直方向布局的起点childTop,然后遍历child,如果当前child是可见的话,会计算每个子child的childLeft的位置,同时childTop随着遍历也变大,这样下一个child就会在当前child下面。调用setChildFrame方法
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
该方法调用child的layout方法,为子child指定位置。这样,ViewGroup在layout方法中完成自己的定位后,又调用onLayout方法调用子child的layout方法,子child通过自己的layout确定了自己的位置,这样一层层传递下去就完成了整个View树的layout过程。
setChildFrame的width和height参数分别是View的测量宽高,我们知道在layout方法中会为成员变量赋值,即
//widht为测量宽度
mRight = left + width;
//height为测量高度
mBottom = top + height;
而View的最终宽高计算为
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
由此可见,在View的默认实现中,View的测量宽高和最终宽高是相同的。区别在于测量宽高形成于measure测量过程,View的最终宽高形成于layout过程中,两者的赋值时机不同。
网友评论