自定义View-MeasureSpec的详细介绍

View类中的onMeasure()方法是用来测量当前View的宽度和高度的,当我们给View设置了LayoutParams以后,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。有3种测量模式:

MeasureSpec.UNSPECIFIED:不确定值,表示子布局想要多大就多大,很少使用

MeasureSpec.AT_MOST:最大值,表示子布局限制在一个最大值内,一般为WRAP_CONTENT

MeasureSpec.EXACTLY:完全准确值,一般是设置了明确的值或者是MATCH_PARENT

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

onMeasure()方法中的两个参数widthMeasureSpec和heightMeasureSpec包含宽和高的信息,一个int整数里,放了测量模式和尺寸大小。我们知道一个int型数据占用32bit,也就是int数据的前面2bit用于区分不同的布局模式,后面的30bit存放的是尺寸的数据。

顶级View(DecorView)和普通View(我们布局中的View)转换MeasureSpec的过程稍有一些不同。顶级View的MeasureSpec由窗口的尺寸和自身的LayoutParams共同确定;普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同确定。

顶级View:在ViewRootImpl中,我们可以看到对顶级View的MeasureSpec的设置:

/**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    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;
    }

可以看到,顶级View的MeasureSpec根据它自身的LayoutParams参数来划分:

LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小。

LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小。

固定尺寸:精确模式,大小为LayoutParams指定的大小。

 

普通View:View的measure过程由ViewGroup来传递,在ViewGroup中通过measureChildWithMargins方法来对子元素进行measure,

/**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    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,

/**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    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:
            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);
    }

通过上边代码我们可以总结出一个父容器的MeasureSpec和子容器的MeasureSpec对应关系:

  exactly at_most unspecified
固定值

exactly:

size等于LayoutParams中指定的大小

exactly:

size等于LayoutParams中指定的大小

exactly:

size等于LayoutParams中指定的大小

match_parent

exactly:

size等于父容器的剩余空间

at_most:

size不会超过父容器的剩余空间

unspecified
wrap_content

at_most:

size不会超过父容器的剩余空间

at_most:

size不会超过父容器的剩余空间

unspecified

左侧是子View的LayoutParams,顶侧是父View的LayoutParams。View是固定值时,不管父容器什么模式,View都是exactly,大小也是其指定的值;View是match_parent时,如果父容器是exactly,View也是exactly,View的size是父容器的剩余空间;如果父容器是at_most,View也是at_most,View的size不会超过父容器的剩余空间;View是wrap_content时,不管父容器是exactly还是at_most,View都是at_most,View的size是不会超过父容器的剩余空间。在实际开发中,unspecified模式主要用于系统内部,一般我们不需要关注。

 

在自定义View的时候,系统会帮我们测量高度和宽度,当我们设置了明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果;当我们设置为WRAP_CONTENT或MATCH_PARENT时,系统帮我们测量的结果就是MATCH_PARENT的长度,所以当我们设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMeasure()方法。

你可能感兴趣的:(自定义View-MeasureSpec的详细介绍)