Android 从0开始自定义控件之深入理解 MeasureSpec (六)

版权声明:欢迎转载,转载请注明出处。 如果本文帮助到你,本人不胜荣幸,如果浪费了你的时间,本人深感抱歉。如果有什么错误,请一定指出,以免误导大家、也误导我。感谢关注。 https://blog.csdn.net/Airsaid/article/details/53576087

转载请标明出处: http://blog.csdn.net/airsaid/article/details/53576087
本文出自:周游的博客

前言

MeasureSpec 是 View 的一个内部类,代表了一个32位的 int 值,高 2 位代表 SpecMode,低 30 位代表 SpecSize。SpecMode 是指测量模式,SpecSize 是指在某种测量模式下的规格大小。.

该类在很大程度上决定了 View 的尺寸规格,之所以说很大程度上是因为这个过程还受父容器的影响,是因为父容器影响 View 的 MeasureSpec 的创建过程。系统会将 View 的 LayoutParams 根据父容器所施加的规则转换成对应的 MeasureSpec,然后再根据这个 MeasureSpec 来测量出 View 的宽高。需要注意的是,这个的宽高是测量的宽高,并不一定是 View 最终的宽高。后面会详细了解 MeasureSpec 的创建过程,在了解之前,首先来了解下 MeasureSpec 的三种测量模式。

三种测量模式

SpecMode 和 SpecSize 组成了 MeasureSpec,MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值来避免过多的对象创建,并提供了对应的打包、解包方法:

public static int makeMeasureSpec(int size, int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}

SpecMode 有三类,分别是:

  • UNSPECIFIED:父容器不对子元素作任何约束,子 View 想要多大就给多大。这种情况一般用在系统内部,表示一种测量的状态。一般情况下,我们不用关注该测量模式。

  • EXACTLY:精准模式。父容器已经解决了子元素所需要的精准大小,这时候子 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值。

  • AT_MOST:最大模式。父最大模式容器指定了一个可用大小即 SpecSize,子元素最大不可以超过指定的这个值。它对应于LayoutParams 中的 wrap_content。

在实际的开发中,我们一般只对AT_MOST进行处理,如:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width = 0;
    int height = 0;

    if(widthMode == MeasureSpec.AT_MOST){
        width = ...
    }

    if(heightMode == MeasureSpec.AT_MOST){
        height = ...
    }

    setMeasuredDimension(widthMode != MeasureSpec.AT_MOST ? widthSize : width,
            heightMode != MeasureSpec.AT_MOST? heightSize : height);
}

MeasureSpec 和 LayoutParams 的对应关系

上面了解了 MeasureSpec 这个类,那么该类是怎么创建来的呢?根据什么创建来的呢?现在就来深入了解一下。

对于普通 View,MeasureSpec 是由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定的。

看看源码,MeasureSpec 是怎么被创建出来的,由于普通 View 的 measure 过程由 ViewGroup 传递而来,所以首先来看一下 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);
}

上述方法,首先获取到了子元素的 LayoutParams 然后根据当前子元素的 LayoutParams 和父类的MeasureSpec,获取了子元素的 MeasureSpec,最后调用了子元素的 measure。

那么在 getChildMeasureSpec 又做了什么操作呢?继续来看一下 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) {
    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 同时结合了子元素的 LayoutParams 来确定了子元素的 MeasureSpec。

由于 UNSPECIFIED 这个模式主要用于系统内部 measure,一般来说,我们无须关注,所以排除掉 UNSPECIFIED 模式来总结一下就是:

  • 当 View 采用固定宽高的时候,不管父容器的 MeasureSpec 是什么,View 的测量模式都是 EXACTLY 也就是精准模式。并且其大小遵循 LayoutParams 中的大小。

  • 当 View 的宽高是 match_parent 的时候,如果父控件是精准模式,那么 View 也是精准模式,并且大小是父容器的剩余空间。如果父控件是最大模式,那么 View 也是最大模式,并且大小不会超过父容器的剩余空间。

  • 当 View 的宽高是 wrap_content 的时候,不管父控件是精准模式还是最大模式,View 的模式最是最大模式,并且大小不能够超过父容器的剩余空间。

参考

  • 《Android 开发艺术探索》

猜你喜欢

转载自blog.csdn.net/Airsaid/article/details/53576087