一、调用流程
Layout流程也是从perfromTravrsals开始,在调用完测量流程后,又调用了performLayout(),这就是Layout流程的起点。
1. ViewRootImpl # performTraversals()
performTraversals()调用了performLayout()。
// ...
if (didLayout) {
// 参数分别是窗口的params和窗口的测量宽高
performLayout(lp, mWidth, mHeight);
// ...
}
// ...
2. ViewRootImpl # performLayout()
performLayout()调用了DecorView的layout()。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// ...
// host就是DecorView
final View host = mView;
if (host == null) {
return;
}
// ...
try {
// 调用DecorView的layout(),传入窗口四个顶点的坐标。
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// ...
} finally {
// ...
}
mInLayout = false;
}
3. ViewGroup # layout()
DecorView自身并没有重写layout(),它的父类FrameLayout也没有,所以其实调用的是ViewGroup的layout()。和measure()类似的是,这也是一个final的方法 ,将窗口的顶点坐标传递到View,调用了View的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);
}
// 调用View的layout()
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
4. View # layout()
layout()中会先调用setFrame()将传入的窗口坐标设置到全局变量,再调用onLayout()去分发layout。
public void layout(int l, int t, int r, int b) {
// ...
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);
// ...
}
// ...
}
5. DecorView # onLayout()
可以写一个demo做下实验,经过上面的步骤,onLayout()调用的确实是DecorView的onLayout()。调用了父类(FrameLayout)的onLayout()。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// ...
}
6. FrameLayout # onLayout()
FrameLayout的onLayout()简单的调用勒兹的layoutChildren(),从方法名也能看出开始分发处理子View的layout了。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
7. FrameLayout # layoutChildren()
在这个方法里完成了layout的分发。遍历子View,调用子View的layout()。
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
// ...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
// 从水平和竖直方向对变量做处理...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
二、View和ViewGroup中的相关方法
View # layout()
- 重新设置了最新的四个相对坐标。
- 调用onDraw()。
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;
}
// mXXX是此View相对于父View的相对坐标
// 先保存下来
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// setFrame()将mXXX变为传入的参数
// 四个顶点确定了,View的位置也就确定了
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 调用onLayout()
onLayout(changed, l, t, r, b);
// ...
}
// ...
}
View # setFrame()
实际上就是重新赋值mLeft、mTop、mRight、mBottom,
protected boolean setFrame(int left, int top, int right, int bottom) {
// ...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// ...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
// ...
}
// ...
}
return changed;
}
View # onLayout()
View中的onLayout()是空实现,具体实现和具体布局有关,所以没有做统一实现。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup # layout()
ViewGroup中并没有做很多工作,而且是一个final的方法,调用了View的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;
}
}
ViewGroup # onLayout()
ViewGroup中的onLayout()是一个抽象方法,ViewGroup是一个抽象类,所以所以继承它的类都需要实现onLayout()。
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
三、测量宽高和实际宽高。
1. 获取方式
测量宽高:
width = view.getMeasuredWidth();
height = view.getMeasuredHeight();
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
实际宽高
width = view.getWidth();
height = view.getHeight();
public final int getWidth() {
return mRight - mLeft;
}
2. 异同点
同:正常情况下测量宽高与实际宽高相等。
mLeft、mRight、mTop、mBottom这四个值是从layout()的参数而来的,而大部分调用layout时,传的参数都是根据getMeasureWidth()和getMeasureHeight()来决定的,所以正常情况下都有mRight - mLeft == mMeasureWidth & MEASURED_SIZE_MASK
,除非修改layout()参数计算的规则。
异:确定时机不同。
测量宽高在measure过程中形成,而实际宽高是在layout赋值mXXX四个顶点坐标后形成的。
网友评论