Layout布局流程
Layout作用是ViewGroup用來確定子元素的位置的。如果當(dāng)前是一個(gè)View,則會(huì)通過layout方法確定當(dāng)前View的位置;如果當(dāng)前是一個(gè)ViewGroup,除了在layout方法中確定當(dāng)前ViewGroup的位置外,還會(huì)調(diào)用onLayout方法分別確定它c(diǎn)hild的位置,如果child中有ViewGroup,還會(huì)繼續(xù)執(zhí)行該過程,直到完成該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);
//...
}
//...
}
首先用局部變量記錄了當(dāng)前View的左上右下的位置,然后調(diào)用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;
}
將新傳入的左上右下的值和成員變量的值對(duì)比,如果有一個(gè)不一樣就證明當(dāng)前布局發(fā)生了變化,changed置為true,changed作為方法的返回值返回。計(jì)算新的寬高和原來的寬高比較,如果有不同就證明View的尺寸也發(fā)生了改變,將新的位置賦值給成員變量,并且回調(diào)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;
}
}
調(diào)用父類View的layout方法,根據(jù)上面的分析,先確定當(dāng)前ViewGroup的位置。然后就調(diào)用了onLayout方法進(jìn)行子child的布局,由于各種ViewGroup的布局特性也不同,所以ViewGroup并沒有實(shí)現(xiàn)onLayout方法,而是由各ViewGroup子類實(shí)現(xiàn)具體的布局方法。以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測(cè)量時(shí)一樣,也是分布局方向進(jìn)行處理,垂直方向的布局調(diào)用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);
}
}
}
根據(jù)LinearLayout垂直方向的Gravity計(jì)算出child垂直方向布局的起點(diǎn)childTop,然后遍歷child,如果當(dāng)前child是可見的話,會(huì)計(jì)算每個(gè)子child的childLeft的位置,同時(shí)childTop隨著遍歷也變大,這樣下一個(gè)child就會(huì)在當(dāng)前child下面。調(diào)用setChildFrame方法
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
該方法調(diào)用child的layout方法,為子child指定位置。這樣,ViewGroup在layout方法中完成自己的定位后,又調(diào)用onLayout方法調(diào)用子child的layout方法,子child通過自己的layout確定了自己的位置,這樣一層層傳遞下去就完成了整個(gè)View樹的layout過程。
setChildFrame的width和height參數(shù)分別是View的測(cè)量寬高,我們知道在layout方法中會(huì)為成員變量賦值,即
//widht為測(cè)量寬度
mRight = left + width;
//height為測(cè)量高度
mBottom = top + height;
而View的最終寬高計(jì)算為
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
由此可見,在View的默認(rèn)實(shí)現(xiàn)中,View的測(cè)量寬高和最終寬高是相同的。區(qū)別在于測(cè)量寬高形成于measure測(cè)量過程,View的最終寬高形成于layout過程中,兩者的賦值時(shí)機(jī)不同。