(二)UI绘制流程 —— 绘制过程源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

从上一篇可以可以知道,UI的绘制流程的起点在 ViewRootImpl 下的 performTraversals()方法。并且按顺序调用了 performMeasure(),performLayout()和 performDraw()这三个方法。

一、MeasureSpec 测量规格

位于 View 类的内部类,由于在安卓布局中有自适应尺寸,而不是全部都是固定的宽高,所以在这里引入 MeasureSpec 来进行测量。MeasureSpec 主要包含的信息是测量模 mode 和测量大小 size 。MeasureSpec 是一个 32 位的数据,高 2 位表示 mode, 低 30 位表示 size 。

测量模式分三种:

EXACTLY :父容器已经测量出所需要的精确大小,这也是 childview 的最终大小
			------match_parent,精确值
ATMOST : child view 最终的大小不能超过父容器的给的
			------wrap_content 
UNSPECIFIED: 不确定,没有限制
			-------一般在 ScrollView,ListView 

点击查看 MeasureSpec 下的 makeMeasureSpec() 方法,返回基于 size 跟 mode 的一个测量规范。

public static int makeMeasureSpec(int size, int mode) {
	if (sUseBrokenMakeMeasureSpec) {
		return size + mode;
	} else {
		   return (size & ~MODE_MASK) | (mode & MODE_MASK);
	}
}

安卓绘制的时候是从最外层开始绘制,然后再一层层往下绘制子控件。这时候父容器的布局就会影响到子容器的布局,MeasureSpec 在这里传递父容器布局对子容器布局的一些限制。

二、Measure()

1.在 performTraversals()下调用 performMeasure() 方法会传进去两个参数,这两个参数在调用的上面初始化。

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

我们点击查看 getRootMeasureSpec() 方法。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
	int measureSpec;
	switch (rootDimension) {

	case ViewGroup.LayoutParams.MATCH_PARENT:
		// Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

getRootMeasureSpec()是用于获取测量跟节点的测量规范,由上一篇我们知道这里的根节点就是我们的 DecorView,并且 DecorView 的布局为 MATCH_PARENT,所以走第一个分支。

2.点击查看我们的 performMeasure()方法。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这时候我们的 mView 是 DecorView,DecorView 本身没有 measure()方法,最终会调到 View 里面的 measure()方法。首先会去判断该容器是否有光影的效果(边沿阴影),有的话会加上去光影的宽高。

boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
	Insets insets = getOpticalInsets();
	int oWidth  = insets.left + insets.right;
	int oHeight = insets.top  + insets.bottom;
	widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
	heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}

在 measure()方法里面没有去进行具体的测量,具体的测量是在 onMeasure()这个方法里面进行。

这是 View 中的 onMeasure(),调用了一个 setMeasuredDimension()方法, setMeasuredDimension() 又直接调用 setMeasuredDimensionRaw(),在这里面进行宽高的直接赋值。getMeasuredWidth()获取的值就是在这里的赋值,所以 getMeasuredWidth()必须在 onMeasure()之后。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

当 DecorView 调用 onMeasure()的时候会调用到 FrameLayout 中的 onMeasure()方法。

在 FrameLayout 中的 onMeasure()中,先通过一个 for 循环对所有的子容器进行遍历。其中有个判断 child.getVisibility() != GONE 的时候才进行测量,这也是为什么 Visibility 设置为 GONE 的时候不占位置的原因。

for (int i = 0; i < count; i++) {
    final View child = getChildAt(i);
    if (mMeasureAllChildren || child.getVisibility() != GONE) {
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        maxWidth = Math.max(maxWidth,
                child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
        maxHeight = Math.max(maxHeight,
                child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        childState = combineMeasuredStates(childState, child.getMeasuredState());
        if (measureMatchParentChildren) {
            if (lp.width == LayoutParams.MATCH_PARENT ||
                    lp.height == LayoutParams.MATCH_PARENT) {
                mMatchParentChildren.add(child);
            }
        }
    }
}

每个子布局会调用 measureChildWithMargins()这个方法,在 measureChildWithMargins()最后又调用 measure()这个方法,如果子容器是 ViewGroup 的话重复上述布局,如果是非 ViewGroup 的 View 容器最终调到前面讲的 View 的 onMeasure()方法,从而结束。

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

这是一个递归的过程,跟上一篇中加载布局是一样的。测量完成之后一样会调用 setMeasuredDimension() 方法进行测量大小的设置。
这里写图片描述

查看 getChildMeasureSpec()这个方法,getChildMeasureSpec()是根据当前父容器的测量规格(里面有测量模式和测量大小)以及子容器的一些属性来确定子容器的测量规格。子容器的测量规格受父容器的测量规格影响。

非 ViewGroup 的 View 的测量:onMeasure 方法里面调用 setMeasuredDimension()确定当前View的大小。
ViewGroup 的测量:1、通过 measureChildWithMargins()、measureChild() 或 measureChildren()三个方法来遍历测量 Child。2、setMeasuredDimension 确定当前ViewGroup的大小。

三、Layout()

performTraversals()下调用完 performMeasure() 后续调用 performLayout()方法。主要的内容在 Layout()方法里,在 Layout()方法里,确认了容器布局的左上右下四个边沿的值,然后调用容器的 onLayout()方法。
我们获取容器的宽度 getWidth() 方法是直接返回 右边沿 - 左边沿,所以 getWidth() 方法要在 Layout()方法之后使用。

可以看见在 View 中 onLayout()是一个空方法,什么都没做。在 ViewGroup 中 onLayout()是一个抽象方法,我们继续查看他的实现类 FrameLayout 下 onLayout()的实现。

FrameLayout 下 onLayout()直接调用 layoutChildren(),在这里面也是一个 for 循环对子容器的遍历,根据 Gravity 属性对子容器的上下左右边沿进行处理。这是 Gravity 属性生效的地方。 最后调用各个子容器的 layout(),子容器是一个 ViewGroup 的话一样又进行了递归。

四、Draw()

performTraversals()下调用完 performLayout() 后续调用 performDraw()方法。一样的主要在 draw()方法里,在 draw()方法里真正开始绘制是在最后面调用的 drawSoftware()方法里。在这里有会调用的 DecorView 的 draw()方法,由于 DecorView 本身没有这个方法,从而就调用到了 View 的 draw()方法。

这是 View 的 draw()方法,很只是很清楚的写出了主要要做的事情。在这里,主要是对背景进行绘制。

 public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // we're done...
            return;
        }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值