Android自定义View之——View的测量

 View的测量是通过函数measure来完成的,measure函数是由final进行修饰的,也就是说子类是无法重写该方法的。
 通过View的源码可以看到,measure里面实际调用了onMeasure方法,该方法是可以被重写的。也就是在自定义View的时候,测量View的尺寸大小通过重写onMeasure方法来完成。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

 可以看到onMeasure方法传入了两个int值,我们先来看看这两个int值是干嘛的?
 其实这里是通过一个32位的二进制值来存储了布局寬高的大小size值和类型mode,其中,高两位存储的是mode(三种类型,高两位存储足矣),低30位存储的大小值,Android为我们提供了一个类MeasureSpec来获取其中的size和mode

int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取width的size
       int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取width的mode

       //下面通过源码看下两个方法,熟悉二进制的移位运算和&运算的不难看出其中的原理如上所述
       private static final int MODE_SHIFT = 30;
       private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
       public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

 接着看一下onMeasure(int widthMeasureSpec, int heightMeasureSpec)中两个值是如何得来的,
子view的measure方法是父viewgroup调用measure时依次调用的,通常构建出MeasureSpec的函数如下

/*
 * spec是来自父布局的MeasureSpec
 * padding为childview的内边距
 * childDimension则为设置的子view具体的寬高值
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);//获取到父view的mode
        int specSize = MeasureSpec.getSize(spec);//获取到父view的size

        int size = Math.max(0, specSize - padding);//可绘制的子view的尺寸大小0 ~ specsize - padding

        int resultSize = 0;//子view的大小
        int resultMode = 0;//子view的mode

        switch (specMode) {//根据父view的mode来设置子view的mode和size
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY://父view的尺寸是确定的,match_parent或精确的值
            if (childDimension >= 0) {//说明子view的size是确定的dp值
                resultSize = childDimension;//大小为子view传进来的size
                resultMode = MeasureSpec.EXACTLY;//子view大小是具体的值,mode为EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//子view尺寸设置为match_parent
                // Child wants to be our size. So be it.
                resultSize = size;//子view的size为之前由父view计算的最大可绘制大小
                resultMode = MeasureSpec.EXACTLY;//子view大小是具体的值,mode为EXACTLY
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子view尺寸设置为wrap_content
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;//子view的size为之前由父view计算的最大可绘制大小
                resultMode = MeasureSpec.AT_MOST;//子view的mode为AT_MOST
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST://父view的大小不能超过其父view的尺寸大小时
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //子view设置了dp值,大小还是由dp决定,所以mode还是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;
                //父view的大小不能超过其父view的尺寸大小,其子view设置为match_parent,
                //子view的大小为父view的尺寸大小,所以mode仍然为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;
                //父view的大小不能超过其父view的尺寸大小,其子view设置为wrap_content,
                //结果还是不能超过该子view的父view的尺寸大小,所以mode仍然为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);//最后将子view的mode和size共同构成一个MeasureSpec,即view中measure传入的值
    }
总结一下:
  1. 当mode为EXACTLY时,表示view的尺寸是精确的值,父view希望子view的尺寸应该是由MeasureSpec来决定的。
    即子view设置了dp或者match_parent,而其尺寸大小在父view中已经设置给MeasureSpec,所以在自定义view时,
    该view的尺寸可直接设置为MeasureSpec获得的size。
  2. 当mode为AT_MOST时,则表示该view的尺寸是不确定的,但是不应该超过父view的尺寸大小,即该view设置了wrap_content,
    此时需要指定该view可绘制的大小值,但不要超过父view的尺寸大小,当自定义view时需要指定具体的大小值(该大小不应超过MeasureSpec获得的size),而不是使用MeasureSpec获取的大小。
  3. UNSPECIFIED是指可以将view的尺寸设置为任意大小的值,一般不常使用。