Android onLayout()布局流程解析
测量与绘制流程文章
Android onMeasure()测量流程解析
Android onDraw()绘制流程解析
1.组件布局流程的那些结论
先看结论再看分析
1.)layout流程始于ViewRootImpl的performLayout()方法,该方法会调用根View(DecorView)的layout()方法进行布局,因为DecorView是ViewGroup(FrameLayout),所以layout流程来到了ViewGroup(其实调用的是父类View类)的layout()方法
2. )layout()方法负责组件自身的布局,会存储mLeft、mTop、mRight、mBottom 四个值来存储自身的位置,如果布局变化,还会回调onSizeChanged()方法通知我们布局发生了变化。最后layout()方法还会调用onLayout()方法进行子组件的布局。
3. )View类因为没有子组件,所以View类的onLayout()方法是个空实现,ViewGroup类因为有子组件,但我们自定义的ViewGroup类都有自己的布局规则,所以ViewGroup类的onLayout()方法是个抽象方法,需要子类去实现,所以我们直接继承ViewGroup类一定需要重写onLayout()方法。
4. )实现ViewGroup的onLayout()方法,我们需要循环所有的子组件,根据我们自己的布局规则,确定他们的位置,然后调用子组件的layout()方法 ,让子组件存储自身的位置,如果子组件是View,则没有后续的布局流程,如果子组件还是ViewGroup,则会重复上述步骤,直到完成所有的布局流程。
2. 源码解析
三大流程始于ViewRootImpl的performTraversals()方法,该方法通过依次调用performMeasure()、performLayout()、performDraw()这三个方法来进行measure、layout、draw流程,所以要了解layout流程我们需要从performLayout方法开始。
我们省略大部分代码,只看我们关注的:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
host就是是DecorView,可以看到,调用了的host的layout()方法,layout()方法的作用就是对自身进行布局,传递的参数分别是0,0,host.getMeasuredWidth,host.getMeasuredHeight。
我们知道DecorView的ViewGroup(FrameLayout),所以我们去查看ViewGroup的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 {
mLayoutCalledWhileSuppressed = true;
}
}
其中四个参数分别代表:
l: 组件左边相对于父布局的左边位置
t: 组件上边相对于父布局的上边位置
r: 组件右边相对于父布局的左边位置
b: 组件底边相对于父布局的上边位置
我们知道,我们开发中通过View.getLeft()、getTop()、getRight()、getBottom()四个方法能获取到上面四个值,分别对应View类的四个变量,mLeft、mTop、mRight、mBottom,所以我们可以判断出在布局过程中,会对这四个变量进行赋值。
public final int getLeft() {
return mLeft;
}
public final int getTop() {
return mTop;
}
public final int getRight() {
return mRight;
}
public final int getBottom() {
return mBottom;
}
可以看到,ViewGroup的layout()方法调用了父类也就是View类的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);
}
}
可以看到,先通过
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
判断布局有没有变化,如果有变化就调用了onLayout(changed, l, t, r, b);方法进行布局。因为setOpticalFrame()最后也调用了setFrame()方法,所以我们直接查看setFrame()方法,代码如下:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
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(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);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
invalidateParentCaches();
}
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
代码非常简单,通过传进来的 left, top, right, bottom与以前存过的mLeft、mTop、mRight、mBottom进行比较,如果不一致,表示布局发生了变化,就重新赋值mLeft、mTop、mRight、mBottom,
可以看到如果不一致还调用了sizeChang()方法,sizeChang()方法右调用了onSizeChanged()方法通知我们布局发生了变化。
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}
由上述可以知道:
1.)mLeft、mTop、mRight、mBottom的赋值是在layout()方法中执行的,如果我们想要得到View的位置信息,那么就应该在layout方法完成后调用getLeft()、getTop()等方法来获取,如果是在此之前调用相应的方法,只能得到0。
2.)layout()方法会调用onLayout();方法进行子组件的布局,我们查看View的onLayout()方法,是一个空实现。这是肯定的,因为View没有子组件,不可能进行布局。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
我们继续查看ViewGroup的onLayout()方法,是一个抽象方法,这也是肯定的,因为如果我们想实现一个布局组件,我们一定要根据我们的规则确定子组件的位置。
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
因为根View是FrameLayout,所以我们去查看FrameLayout的onLayout()方法来理解布局的流程,源码如下:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false );
}
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();
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();
int childLeft;
int childTop;
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;
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;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
逻辑也比较简单,循环所有子View,根据子View的LayoutParams里设置的gravity、margin,FrameLayout自身的padding等确认每个子View的位置,调用子View的layout()方法,进行后续的layout流程。