在上一次的源码分析中知道了performTraversals()是整个UI绘制流程的入口,通过调用performMeasure()进行UI布局的测量

测量流程

performMeasure

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

通过传递参数childWidthMeasureSpecchildHeightMeasureSpecperformMeasure()进行了调用,然后在performMeasure()调用了view.measure()方法。

MeasureSpec

childWidthMeasureSpecchildHeightMeasureSpec来源如下:

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
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;
}

然后在这里看到了一个对象MeasureSpec。作用是在measure流程中,系统将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec(规格), 然后在onMeasure中根据这个MeasureSpec来确定view的测量宽高。

   public static class MeasureSpec {
 
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
     /**
      * UNSPECIFIED 模式:
      * 父View不对子View有任何限制,子View需要多大就多大
      */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
 
    /**
      * EXACTYLY 模式:
      * 父View已经测量出子Viwe所需要的精确大小,这时候View的最终大小
      * 就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式
      */
    public static final int EXACTLY     = 1 << MODE_SHIFT;
 
    /**
      * AT_MOST 模式:
      * 子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值,
      * 即对应wrap_content这种模式
      */
    public static final int AT_MOST     = 2 << MODE_SHIFT;
 
    //将size和mode打包成一个32位的int型数值
    //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
 
    //将32位的MeasureSpec解包,返回SpecMode,测量模式
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
 
    //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
    //...
}

测量模式

EXACTLY :父容器已经测量出所需要的精确大小,这也是childView的最终大小
        ------match_parent,精确值是爸爸的

ATMOST : child view最终的大小不能超过父容器的给的
        ------wrap_content 精确值不超过爸爸

UNSPECIFIED: 不确定,源码内部使用
        -------一般在ScrollView,ListView

规格

measurespec1

View#measure

看完MeasureSpec后,我们来看看mView.measure()方法,在这mView就是DecorView,这也就说明了

无论是measure过程、layout过程还是draw过程,永远都是从View树的根节点开始测量或计算(即从树的顶端开始),一层一层、一个分支一个分支地进行(即树形递归),最终计算整个View树中各个View,最终确定整个View树的相关属性。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        // ...
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {
            // ...
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
            //  ...
        }
    }
}

可以看到,内部调用了onMeasure方法。不管是LinearLayout或者是FreamLayout还是其他布局, 他们都是通过测量组件,实现我们的布局定位,每一个Layout的onMeasure实现都不一样。

FrameLayout来讲,根据它的MeasureSpec来对每一个子View进行测量,即调用measureChildWithMargin方法, 对于每一个测量完成的子View,会寻找其中最大的宽高,那么FrameLayout的测量宽高会受到这个子View的最大宽高的影响(wrap_content模式),接着调用setMeasureDimension方法,把FrameLayout的测量宽高保存。最后则是特殊情况的处理,即当FrameLayout为wrap_content属性时,如果其子View是match_parent属性的话,则要重新设置FrameLayout的测量规格,然后重新对该部分View测量。

resolveSizeAndState

setMeasureDimension方法用于保存测量结果,该方法的参数接收的是resolveSizeAndState方法的返回值。

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

可以看到该方法的思路是相当清晰的,当specMode是EXACTLY时,那么直接返回MeasureSpec里面的宽高规格,作为最终的测量宽高;当specMode时AT_MOST时,那么取MeasureSpec的宽高规格和size的最小值。

measureChildWithMargins

在FrameLayout测量内提到的measureChildWithMargin方法,它接收的主要参数是子View以及父容器的MeasureSpec,所以它的作用就是对子View进行测量。

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

里面调用了getChildMeasureSpec方法,把父容器的MeasureSpec以及自身的layoutParams属性传递进去来获取子View的MeasureSpe,在这里我们可以看到直接又调用了子类的measure。

总结

测量流程:在performTraversals开始获得DecorView种的系统布局的尺寸,然后在performMeasure方法中开始测量流程,对于不同的layout布局有着不同的实现方式,但大体上是在onMeasure方法中,对每一个子View进行遍历,根据ViewGroup的MeasureSpec及子View的layoutParams来确定自身的测量宽高,然后最后根据所有子View的测量宽高信息再确定父view的宽高, 不断的遍历子View的measure方法,根据ViewGroup的MeasureSpec及子View的LayoutParams来决定子View的MeasureSpec,进一步获取子View的测量宽高,然后逐层返回,不断保存ViewGroup的测量宽高。