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;
}
}
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
ViewGroup重写的layout是final修饰的,不能重写,onLayout是abstract修饰的,必须要重写,让子类自己实现自己需要的布局效果,重写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;
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// 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;
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
上诉代码可以看到,什么时候会调用onLayout(),也发现getMeasureWidth(),和getWidth()的区别;
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
熟悉了执行的流程,从两个方法中就可以看出来区别:
mMeasuredWidth方法在onMeasure时就会给每一个view获取到MeasureSpec的属性。所以在Measure流程执行过后,就可以得到测量的宽高。
getWidth方法需要属性mRight - mLeft,才能得到具体的值,但是这两个值在setFrame方法执行的时候才回去赋值,所以在layout执行完之后才能得到值。layout之后完后得到的值才是View的实际大小。
下面看下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);
}
}
根据不同方向,来做不同的布局。
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; //计算child可使用的空间大小。用宽度 - padding值
final int count = getVirtualChildCount(); //垂直方法子view的个数
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; //判断LinearLayout的Gravity属性是什么,只是top从顶部,中间,还是底部开始
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++) { //遍历每一个子view
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth(); //测量子view的宽高,确定子view的宽和高
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity; //确定子view的位置。
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;
}
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height); //setChildFrame实际上就是调用child的layout方法
}
看到这里我们就清楚了,ViewGroup首先调用了layout方法确定自己本身在父View的位置(ViewRoot),然后调用onLayout确定每个子view的位置,每个子View会调用自己的layout,确定自己在ViewGroup的位置。
简单的说,就是View的layout方法用于view确定自己本身在父View的位置,ViewGroup的onLayout方法用来确定子View的位置。