Layout起布局作用,确定View的位置。当ViewGroup确定位置后,会遍历子View并调用其layout方法确定子View的位置。
View的onLayout方法
View的onLayout方法是一个空实现,因为子View不包含其他View,不需要被调用。
ViewGroup的onLayout方法
ViewGroup会先调用layout方法:
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
ViewGroup的onLayout方法是一个抽象方法,由子类去实现,主要是让ViewGroup在这个方法确定每个子View的位置。
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
我们需要关心的其实就是这个方法,以LinearLayout为例:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical();
} else {
layoutHorizontal();
}
}
根据android:orientation属性给子View设置不同的位置,看layoutVertical方法代码:
void layoutVertical() {
final int paddingLeft = mPaddingLeft;
int childTop = mPaddingTop;
int childLeft;
// Where right end of child should go
final int width = mRight - mLeft;
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.HORIZONTAL_GRAVITY_MASK;
if (majorGravity != Gravity.TOP) {
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already, we add the top
// padding to compensate
childTop = mBottom - mTop + mPaddingTop - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
childTop += ((mBottom - mTop) - mTotalLength) / 2;
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;
}
switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
childLeft = paddingLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
default:
childLeft = paddingLeft;
break;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
在layout时,会用到子View测量后的值,所以先测量,后布局。最后调用setChildFrame方法:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
调用子View的layout方法确定位置。
我们自定义流式布局时,就需要像这样重写onLayout方法。