View onMeasure


视图测量的入口在ViewRootImpl类,一次performTraversals过程,测量、布局和绘制流程,从它的measureHierarchy方法开始,分析视图测量过程。

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, 
            final int desiredWindowHeight){
    int childWidthMeasureSpec;
    int childHeightMeasureSpec;
    boolean windowSizeMayChange = false;

    boolean goodMeasure = false;
    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
        ...
    }

    if (!goodMeasure) {//第二步
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()){
            windowSizeMayChange = true;
        }
    }
    return windowSizeMayChange
}

树形结构层次测量,入参desiredWindowXxx代表希望获取的窗体宽高。若第一次触发performTraversals方法,将其设置为屏幕像素,我使用的测试手机分辨率是1080x1920。
ViewRootImpl#performTraversals方法代码段。

final Rect mWinFrame; //ViewRootImpl类中定义
Rect frame = mWinFrame;
if (mFirst) {
    ...
    DisplayMetrics packageMetrics = mView.getContext().
                    getResources().getDisplayMetrics();
    desiredWindowWidth = packageMetrics.widthPixels;
    desiredWindowHeight = packageMetrics.heightPixels;
}else {
    ...
    desiredWindowWidth = frame.width();
    desiredWindowHeight = frame.height();
    if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;
        windowSizeMayChange = true;
    }
}

初始宽高设置屏幕值是分辨率.PNG
由图可见,宽高是 1080x1794,因为需要去除 状态栏,高度heightPixel比1980小一些。 第一次触发时,内部的mWidth、mHeight还未设置,都是0。返回的windowSizeMayChange是true。
第一次时,继续relayoutWindow方法,frame初始化(0,0,1080,1920)。向mWidth与mHeight赋值。
ViewRootImpl#performTraversals方法代码段。

//设置mWidth、mHeight与mWinFrame区域相同。
if (mWidth != frame.width() || mHeight != frame.height()) {
    mWidth = frame.width();
    mHeight = frame.height();
}

所以,在下一次时,上面代码中mWidth与desiredWindowWidth相同,返回windowSizeMayChange是false,不会再执行relayoutWindow,除非窗体frame又发生了变化。
继续走代码,此时发现mHeight是1920,但是测量的DecorView的measureHeight是1794,再测一次。

View onMeasure_第1张图片
再测量一次.PNG
以Wms返回的窗体宽高1080x1920为准。
继续回到measureHierarchy方法分析。LayoutParams布局参数,创建时,默认MATCH_PARENT,直接到第二步,getRootMeasureSpec方法根据desiredWindowXxx与模式(MATCH_PARENT)获取 顶层视图的MeasureSpec。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

MeasureSpec两部分信息模式与数值,MeasureSpec传给子视图,根视图DecorView第一个子视图。模式根据MATCH_PARENT、WRAP_CONTENT判断。
EXACTLY模式:代表子视图对宽高的要求是确定的,windowSize。这里就是desiredWindowXxx,DecorView就是这种模式,大小与desiredWindowXxx一致。
AT_MOST模式:代表子视图对宽高的要求不确定,最大不能超过窗体提供的windowSize,传给子视图时,最大值限定为desiredWindowXxx。

ViewRootImpl构造方法mWidth和mHeight初始化为-1。
第一次performTraversals测量,getMeasuredWidth的值与mWidth不同,返回值windowSizeMayChange设置true。
第二次performTraversals测量,mWidth已初始化窗体frame的值,desiredWindowWidth也设置frame的值,最终测量值是相同的,返回值windowSizeMayChange设置false。

在ViewRootImpl类中的测量过程比较复杂,主要涉及根据窗体大小计算顶层视图的MeasureSpec,然后,通过顶层视图的measure方法进入树形结构测量。窗体区域由WindowSession#relayout方法远程访问Wms服务获取。

一、视图树 measure 流程

从顶层开始,ViewRootImpl 类的 performMeasure() 方法。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
    }
}

内部顶层视图 DecorView measure 测量。
父类是 FrameLayout ,FrameLayout 和 ViewGroup 都没有重写 measure() 方法,基类 View 的 measure() 方法。

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

View 类的 measure() 方法,final 修饰,表示系统不希望子类重写它,它提供测量的一套模版设计步骤,不可被改变,即调用 onMeasure() 方法。

子类的测量需求,都会调用 View measure() 方法。

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

子类重写 onMeasure() 方法,实现满足需要的测量方法。
容器和非容器视图的测量方法不同,容器视图包括子视图测量,因此特定视图的实际测量方法特定分析。如果不重写,getDefaultSize() 方法,默认测量值。建议值由系统背景 Drawable 和设置的最小宽高决定。

DecorView 视图的 onMeasure() 方法,调用父类 FrameLayout 的 onMeasure() 方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    //遍历子视图
    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);
                }
            }
        }
    }
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    ...
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
}

遍历子视图,触发非GONE子视图 measure() 方法。

如果子视图是容器视图,递归遍历它的子视图,完成整个树形结构的测量。

View onMeasure_第2张图片
树形视图结构测量顺序图

顶层视图—>>节点1—>>节点4—>>节点5—>>节点8—>>节点9—>>节点2—>>节点6—>>节点3—>>节点7

遍历子视图后,执行几次 measureChildWithMargins()方法,记录子视图最大测量宽高,FrameLayout 类布局的子视图是层叠覆盖的,最大测量值可以反映出子视图占用区域。

最后,resolveSizeAndState() 方法,根据这个最大值 maxXxx 和传入的 MeasureSpec 计算父容器 Size,即 DecorView 的 Size。

DecorView 是 MATCH_PARENT,属于 EXACTLY 模式,根据传入的 XxxMeasureSpec 计算出 Size,确定 DecorView 宽高值,其他 FrameLayout 布局若设置 WRAP_CONTENT,即 AT_MOST 模式,由子视图占据区域最大值计算,要比较 Size 和 maxXxx。

setMeasuredDimension() 方法,将测量值交给视图内部的 mMeasuredXxx,代表测量宽高。

二、如何计算子视图MeasureSpec

根据子视图LayoutParams记录的宽高和父容器对他的区域限制规则一,在ViewGroup的measureChildWithMargins方法,计算MeasureSpec。

MeasureSpec一共32位,模式+数值。高两位xx代表模式,低30位代表数值。
模式:
EXACTLY:视图精确大小,也是最终测量值,固定值和match_parent是这种模式。
AT_MOST:视图大小不能大于父视图指定的值。
UNSPECIFIED:无限制。

上面FrameLayout的onMeasure方法中,测量子视图,需要计算子视图的MeasureSpec,然后在调用子视图measure方法时传递给它。

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //计算子视图MeasureSpec。
    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);
}

读取子视图LayoutParams的宽/高,子视图margin+父容器padding,根据父视图Spec模式和数值,一起计算childXxxMeasureSpec。有以下几种情况。
父视图模式是EXACTLY

子视图的lp.width是>0的精确值,模式EXACTLY,SpecSize是lp.width。
子视图的lp.width是MATCH_PARENT,和父视图一致,模式EXACTLY,SpecSize设置是父MeasureSpec获取的Size。
子视图的lp.width是WRAP_CONTENT,子视图模式AT_MOST,SpecSize设置为父MeasureSpec获取的Size,表示子视图不确定,但最大不可超过Size。

父视图模式是AT_MOST

子视图的lp.width是>0的精确值,模式EXACTLY,SpecSize是lp.width。
子视图的lp.width是MATCH_PARENT,和父视图一致,但父视图Size不确定,只有一个最大Size,所以子视图模式AT_MOST,SpecSize设置父MeasureSpec获取的最大Size,即与父保持一样的最大Size。
子视图的lp.width是WRAP_CONTENT,子视图模式AT_MOST,Size设置为父MeasureSpec获取的Size,表示子视图不确定,最大不可超过Size。

总结

1,从顶层视图开始测量,单个视图的测量入口都是View的measure方法,View和ViewGroup都是这样。
2,每个子视图测量方法不同,需要重写onMeasure方法,测量具体值,并设置,不重写时,将按照View中默认建议的值setMeasuredDimension方法设置。
3,容器视图onMeasure方法,不仅测量自己,还需要measureChildren方法考虑子视图。
4,父视图根据自己的MeasureSpec和子视图LayoutParams,计算子视图的MeasureSpec,在测量时,MeasureSpec层层传递,贯穿始终。
5,视图提供的measure方法,是一个确定的测量步骤,子类不能重写,一旦视图有测量需求,就会到这个方法中,模版设计模式。
6,当执行了setMeasuredDimension方法,就设置了测量值,可以通过getMeasuredHeight方法获取了。


任重而道远

你可能感兴趣的:(View onMeasure)