layout 过程作用是 ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout 中遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法 确定View 本身的位置,而 onLayout方法 则会确定所有子元素的位置。
ViewRootImpl
View 的 layout 起始点也是从 ViewRootImpl 开始的,ViewRootImpl 的 performLayout 方法会调用 DecorView 的 layout 方法来启动 layout 流程。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
....
mInLayout = true;
// 将全局变量mView(DecorView)赋值给host
final View host = mView;
try {
// host.getMeasuredWidth() 屏幕宽、host.getMeasuredHeight() 屏幕高
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
.....
}finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
从上面performLayout() 方法可以看出,host.layout() 方法也就是View.layout 方法,layout()方法的四个参数分别是当前View的左(left),上(top),右(right),下(bottom)。
View
layout 是 View 类中的方法,传入的四个参数即我们熟知的 left、top、right、bottom,这四个值都是 View 相对父容器 ViewGroup 的坐标值。
// View 源码
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;
}
//保存上一次View的四个点的位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// isLayoutModeOptical 判断mParent是否ViewGroup 并且布局是否有变换
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//在第一次或是位置改变时changed=true 条件成立视图View重新布局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
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);
}
}
}
final boolean wasLayoutValid = isLayoutValid();
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
.....
}
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
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);
}
.....
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
从上面源码看, 在View 的layout() 方法中通过setFrame 方法来设定View 的四个顶点位置,即初始化mLeft、mTop、mBottom、mRight四个值,View 的四个顶点一旦确定,那么View 在父容器中的位置也就确定了;如果View 类型是ViewGroup ,接着会调用onLayout方法,确定子元素在父容器中的位置,onLayout 的具体实现同样和具体的布局有关,所以View和ViewGroup均 没有真正实现onLayout方法。
ViewGroup
layout 方法又会调用自身的 onLayout 方法。onLayout 方法在 View 类中是空实现,大部分情况下 View 都无需重写该方法。而 ViewGroup 又将其改为了抽象方法,即每个 ViewGroup 子类都需要通过实现该方法来管理自己的所有 childView 的摆放位置,FrameLayout 和 LinearLayout 等容器类就通过实现该方法来实现不同的布局效果
// ViewGroup 源码
@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;
}
}
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
从上面源码来看,ViewGroup 的layout方法最终还是调用了View 的layout 方法。onLayout 方法 是抽象方法,由ViewGroup 子类去实现该方法。下面可以查看下,ViewGroup 子类的FrameLayout 是如何实现的。
FrameLayout
// FrameLayout 源码
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍历所有FrameLayout下的子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//判断当前子View的可见度。不过是GONE那么就不进行布局
if (child.getVisibility() != GONE) {
//获取子View的getLayoutParams
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
//根据子View的LayoutParams来获取gravity的设置
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//下面都是根据gravity属性的设置来决定如何设置子View四个点的值
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//设置完成子View的四个点的值传入子View的layout方法开始布局
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
从上面的代码来看,FrameLayout直接调用了layoutChildren()方法,在这个方法中完成每个子View的布局。这个方法中通过对对齐方式和Margin的计算,来获得子View四个点的位置,最后调用child.layout()方法,如果是View就会走上面View的布局;如果是ViewGrouop那么就和上面FrameLayout的布局逻辑一致。 (这里说的逻辑一致是因为直接继承ViewGroup的容器都会根据自己的特点重写onLayout()方法。比如LinearLayout和FrameLayout的布局方式是不同。但是最后都是会调用child.layout()方法,也就是逻辑都是一样的)
补充
类型 | 作用 | 赋值时机 | 使用场景 |
---|---|---|---|
getMeasuredWidth() / getMeasuredHeight() | 获得 View测量的宽 / 高 | measure过程 | 在layout()过程中获取宽/高 |
getWidth() / getHeight() | 获得View最终的宽 / 高 | layout过程 | 在除layout()以外得放中获取宽/高 |