Layout过程的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定之后,它会在onLayout方法中遍历所有的子元素并调用其layout方法,在layout方法中onLayout又会被调用。layout过程和measure过程相比就简单多了,layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。先看View的layout方法。代码如下:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
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;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
layout方法的大致流程如下:
1)首先会通过setFrame()方法来设定View的四个顶点的位置,也就是初始化mLeft,mRight,mTOP,mBottom这四个值,View的四个顶点一旦被确定,那么View在父容器中的位置也就确定了;这里用于确定在父容器中的位置。
2)接着会电泳onLayout方法,这个方法的用途是父容器确定子元素的位置,和onMeasure方法类似,onLayout的具体实现和具体的布局有关,所以View和ViewGroup均没有真正实现onLayout方法。View中的onLayout方法如下:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
下面看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);
}
}
LinearLayout中onLayout的实现逻辑和onMeasure方法实现逻辑类似,这里选择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;
}
<span style="white-space:pre"> </span>//开始遍历子元素
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);
}
}
}
layoutVertical的实现逻辑如下,此方法会遍历所有所有的子元素并电泳setChildFrame方法来为子元素指定对应的位置,其中childTop会逐渐增大,这就意味着后面的子元素会被放在靠下的位置,这刚好符合竖直方法的LinearLayout的特性。至于 setChildFrame,它仅仅是调用了子元素的layout方法而已,也就是确定子元素的位置以及子元素的子元素的位置,这样父元素在layout方法中完成自己的定位以后,就通过onLayout方法去调用子元素的layout方法,子元素会通过自己的layout方法来确定自己的位置,这样一层一层对的传递下去就完成了整个View树的layout过程。
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
setChildFrame中的width和height实际上就是子元素的测量宽高,在layoutVertical中有如下代码
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();<pre name="code" class="java"><span style="white-space:pre"> </span>setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);
可以想象left + width就是Right,top + height就是Bottom。在View的layout方法中会通过setFrame去设置子元素的四个顶点的位置,setFrame就是确定该View的位置,setFrame的代码如下:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
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;
if (mDisplayList != null) {
mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
if (mTransformationInfo != null) {
mTransformationInfo.mMatrixDirty = true;
}
}
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
setFrame中有几句代码:
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
if (mDisplayList != null) {
mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
DisplayList mDisplayList;
最后通过DisplayList的setLeftTopRightBottom方法布局。这样一来子元素的位置就确
定了。