OnMeasure源码分析

什么是MeasureSpec

在看代码之前,先要了解MeasureSpec这个概念
MeasureSpec是View的一个静态内部类,用于记录view的大小;通过父View的measurespec和子view的layoutparams可以得到子view的MeasureSpec
MeasureSpec由specSize和specMode组成,为了节省内存,压缩成一个32位的int值


他的mode有三种:
1. UNSPECIFIED:父view对子view没有任何限制,可以为子view想要的任何值
2.EXACTLY:如果子view没有设置确切的大小,将由父view决定
3.AT_MOST:在不超过父view大小的情况下,子view自己决定多大

View的onMeasure流程

View的Measure过程是从ViewRoot的performTraversals()方法开始的,在viewrotImpl的performTraverSals()方法中可以看到

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


可以看到performMeasure(widthMesaureSpec.heightMeasureSpec)的参数是来自getRootMeasureSpec(mWidth,lp.width),再看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;
    }


这里的第一个windowSize为当前window可用的宽度或高度,第二rootDimension传入的是我们在xml或者layoutparams中定义的宽和高,有三种,具体值,match_parent,wrap_content;通过getRootMeasureSpec()方法生成measureSpec。

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

performMeasure()方法里比较简单,直接调用mView.measure();measure()里又会调用到onMeasure(widhtMeasureSpec,heightMeasureSpec),参数也是来自前面performTraversals()中生成的

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


先来看getSuggestedMiniumWidth()和getSuggestedMiniumHeight()

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    
public int getMinimumHeight() {
        return mMinHeight;
    }


这两个方法都是返回当前view可能的最小宽高度。

再看getDefaultSize()方法

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


这里返回的result是int,不是MeasureSpec;
- 在UNSPECIFIED模式下,返回的是前面getSuggestedMinWidth()或者getSuggestedMinHeight()的值
- 在AT_MOST和EXACTLY模式时,返回的是measureSpec中的测量值。

最后,onmeasure()调用setMeasureDimension()

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }


这里做的就是将算出的MeasureWidth和MeasureHeight进行赋值,只有这步之后,view.getHeight()或者view.getWidth()才有值。

至此,onMeasure()的流程就完成了。

ViewGroup的onMeasure流程:

在ViewGroup中,通常不止一个view,所以要对它的子view一个个进行测量大小,在measureChildren()方法做这件事

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

代码也比较清楚,就是对一个个子view调用measureChild()

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

来看getChildMeasurespec方法

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //这里spec是父view的measureSpec,所以specSize和specMode是父类的
        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:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

getChildMeasureSpec总结下来就是,根据父view的MeasureSpec,来确定子view的测绘类型和大小,写成表格如下。

父view/子view childDimension >= 0 childDimension = WRAP_CONTENT childDimension =MATCH_PARENT
EXACTLY EXACTLY AT_MOST EXACTLY
AT_MOST EXACTLY AT_MOST AT_MOST
UNSPECIFIED EXACTLY UNSPECIFIED UNSPECIFIED

这里通过getChildMeasureSpec()获取子view的宽高measureSpec,然后调用child.measure(),又会走到view的measure()进行递归。

猜你喜欢

转载自blog.csdn.net/h15620535082/article/details/81253217