Android 源码解析measure机制-深入浅出

1、ViewRootImpl.performTraversals方法

ViewRootImpl是一个Activity UI显示的一个控制类,在View和WindowManager之间起一个中间管理的角色。该类的performTraversals方法里面会相继调用performMeasure、performLayout、performDraw三个方法,继而调用根布局DecorView的mersure、layout、draw方法,DecorView又会调用其孩子的mersure、layout、draw方法,进而完成一个Activity的绘制。本文只关注measure方面的流程,我们先看一下performMeasure调用的地方

private void performTraversals() {
    ......
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
           + mWidth + " measuredWidth=" + host.getMeasuredWidth()
           + " mHeight=" + mHeight
           + " measuredHeight=" + host.getMeasuredHeight()
           + " coveredInsetsChanged=" + contentInsetsChanged);

    // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
}

我们看到传给performMeasure的两个参数是childWidthMeasureSpec和childHeightMeasureSpec,这是两个int类型的的变量。childWidthMeasureSpec里面又包含了两个部分mode和size,一个int变量是32位的,最高两位表示mode,其余30位用来存放size,childHeightMeasureSpec同样,这两个变量分别存放了宽和高的信息。我们看下getRootMeasureSpec方法,看看这两个变量是如何生成的。我们以宽为例,给getRootMeasureSpec方法传的参数值mWidth和lp.width,mWidth可以理解为Window的宽度,lp是WindowManager.LayoutParams类的实例,其在声明的时候已经初始化了

final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

 

public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
        }
public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }

可知,lp.width默认值就是LayoutParams.MATCH_PARENT,lp.height的默认值以为LayoutParams.MATCH_PARENT。

2、ViewRootImpl.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;
    }

如果rootDimension等于ViewGroup.LayoutParams.MATCH_PARENT,即-1,那么size为windowSize,即屏幕的宽或者高,mode的值为MeasureSpec.EXACTLY。如果rootDimension等于ViewGroup.LayoutParams.WRAP_CONTENT,即-2,那么size为windowSize,即屏幕的宽或者高,mode的值为MeasureSpec.AT_MOST。否则,rootDimension为一个具体的尺寸值,那么size为rootDimension,mode的值为MeasureSpec.EXACTLY。在第1节中,我们知道该方法传进来的参数windowSize为窗口的宽和高,rootDimension的值为ViewGroup.LayoutParams.MATCH_PARENT,所以该方法返回值所包含的size就是窗口大小,mode值为MeasureSpec.EXACTLY。

3、ViewRootImpl.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.measure方法,而mView就是Activity的根布局DecorView,而DecorView是FrameLayout的子类,并且没有重写measure方法,所以最终调用的还是View.measure方法,参数childWidthMeasureSpec和childHeightMeasureSpec的含义在第2节中说过,传给DecorView的宽高信息size部分就是window的宽和高,mode就是MeasureSpec.EXACTLY,DecorView给其孩子view传的时候mode值就具体问题具体分析了,不固定。

4、View.measure方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }

为了突出重点,其他代码都省略了,我们只需关注view的measure方法里面调用了onMeasure方法。

5、View.onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

这里出来一个新的mode值MeasureSpec.UNSPECIFIED,这里先说明一下,这个值其实没有用到,无需关注太多,至于为什么说没有用到,首先ViewRootImpl给DecorView传的时候其实就是只传了个MeasureSpec.EXACTLY,而DecorView给其孩子传的时候没有用到MeasureSpec.UNSPECIFIED下文会讲。getDefaultSize方法就是,如果mode是MeasureSpec.EXACTLY或者MeasureSpec.AT_MOST,那么自己的宽和高就会设置成parent传进来的宽和高,看到这里,我们可以推想,view的宽和高岂不总是和parent宽和高相同了,这个实际开发遇到的不一样啊,子view的宽高绝大多数都比parent的小啊,其实这也不奇怪,因为我们遇到的容器类如FrameLayout或者非容器类比如TextView都重写了onMeasure方法。目前只是跟到了DecorView,其就重写了onMeasure方法。其实容器类的onMeasure方法最终都会调用measureChild或者measureChildWithMargins方法来对其孩子进行测量,我们看一下measureChildWithMargins的方法。

6、ViewGroup.measureChildWithMargins方法

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

该方法最后一句代码可知,容器类的measure方法最终会调用子view的measure方法,我们来看一下给子view的参数是如何产生的,看getChildMeasureSpec方法,以宽为例,看一下getChildMeasureSpec的三个参数的含义,第一个参数parentWidthMeasureSpec,就是当前view的从其parent接收到的宽测量值,第二个参数是mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed,widthUser一般为0,写过布局的都知道,这个应该就是计算子view距parent左右两边的距离和,第三个参数lp.width就跟view的具体配置有关了,可能值为三个ViewGroup.LayoutParams.MATCH_PARENT、ViewGroup.LayoutParams.WRAP_PARENT和一个具体的尺寸值(大于等于0)。下面来看一下getChildMeasureSpec内部实现。

7、ViewGroup.getChildMeasureSpec方法

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            ......
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

为了方便理解上面代码,我画了个图如下:

容器类measure的大致流程就先循环调用子View的measure,当然子View也可以是容器类,这样层层向下调用,所有的子View测量完了,然后容器类再对自己进行测量。另外,第5节说过MeasureSpec.UNSPECIFIED这个值没有用,这里接着解释一下,首先从DecorView传的时候就没有MeasureSpec.UNSPECIFIED,从上图可知针对parent的MeasureSpec.EXACTLY和MeasureSpec.AT_MOST,给子view传的时候,也不会传MeasureSpec.UNSPECIFIED,所以说该值在measure的过程中可以认为没有用到。

 

以上是本人针对measure流程做的一些梳理,如有问题欢迎指出。

 

 

 

 

AT_MOST

由上图可知

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android海纳百川

打赏加微信,送跑车加管理

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值