Android UI绘制流程分析(四)layout

源码版本Android 6.0
请参阅:http://androidxref.com/6.0.1_r10

本文目的是分析从Activity启动到走完绘制流程并显示在界面上的过程,在源码展示阶段为了使跟踪代码逻辑更清晰会省略掉一部分非主干的代码,具体详细代码请翻阅源码。

上一篇我们分析到UI绘制过程中的measure流程,接下来我们来分析一下layout过程,layout过程相对于measure过程简单很多,还是从ViewRootImpl#performTraversals()方法调用performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)开始,我们来分析一下从最顶层DecorView开始布局的流程:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
	// 标识是否layout完成
    mLayoutRequested = false;
    mScrollMayChange = true;
    // 标识当前是否正在执行该View的layout流程
    mInLayout = true;

    final View host = mView;
   	...
    try {
    	// 执行DecorView的layout流程
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
        ...
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

由于DecorView extends FrameLayout,所以会调用到FrameLayout没有实现layout(int l, int t, int r, int b)所以最终调用ViewGroup#layout(int l, int t, int r, int b)

#class in ViewGroup

public final void layout(int l, int t, int r, int b) {
    ...
        super.layout(l, t, r, b);
    ...
}

最终调用View#layout(l,t,r,b)

#class in 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;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    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);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        ...
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

View#onLayout(changed,l,t,r,b)是个空实现,所以会走到FrameLayout中:

#class in FrameLayout

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();

	// 上面几行代码获取到父控件的上下左右,接下来根据每个子View的布局参数对每一个子View进行摆放
    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);
        }
    }
}

layout

整个layout过程如下:

  1. 执行ViewRootImpl#performLayout()方法,内部会调用host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());,layout的调用过程是:
    DecorView -> FrameLayout -> ViewGroup ->View
    最终是回调到View#layout()
  2. View#layout()中判断如果布局有改变则会回调onLayout(changed, l, t, r, b);,此时代码走向是:
    View -> FrameLayout
    最终回调到FrameLayout#onLayout() -> FrameLayout#layoutChildren()先对自己布局然后对所有的子View进行布局
  3. 对子View布局的过程是又回调child.layout(childLeft, childTop, childLeft + width, childTop + height);

到这里布局就结束了。

注意:

当我们自定义View时,如果自定义的View是一个非容器类视图,那么直接继承自View,在无特定需求的情况下onLayout(boolean changed, int left, int top, int right, int bottom)可以不实现。但是当你定义的是一个容器类View,那需要继承自ViewGroup,此时protected abstract void onLayout(boolean changed, int l, int t, int r, int b);是个抽象方法必须实现,然后在内部对子视图进行layout。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值