1.首先明确自定义view测量的目的:确定自身的大小。

首先 onMeasure(int widthMeasureSpec, int heightMeasureSpec);方法时parent给子View的约束条件。里面包含了该子view的mode和建议的大小尺寸。

     实际中对于match_parent和精确的数值大小系统已经给我们解决了,一般不需要修改,我们只需处理好warp_content的情况就好了。

      1.1 当自定义View继承View.

             这个时候只需根据parent view给出的测量规则确定自身大小即可。如下模板所示

/**
     * 获取合适的大小
     *
     * @param defaultSize 默认大小
     * @param measureSpec 测量规格
     */
    private int getProperSize(int defaultSize, int measureSpec) {
        int properSize = defaultSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                // 没有指定大小,设置为默认大小
                Log.d(TAG,"UNSPECIFIED");
                properSize = defaultSize;
                break;
            case MeasureSpec.EXACTLY:
                // 固定大小,无需更改其大小
                Log.d(TAG,"EXACTLY");

                properSize = size;
                break;
            case MeasureSpec.AT_MOST:
                Log.d(TAG,"AT_MOST");
                // 此处该值可以取小于等于最大值的任意值,此处根据实际情况自行确定
                properSize = size >defaultSize?defaultSize:size;
        }

        return properSize;
    }

 

1.2 自定义ViewGroup。

                 我们想要确定自身的大小,需要知道孩子的大小,ViewGroup提供了measureChild方法.measureChild方法根据父控件的测量规则和子控件的layoutParams来通过方法getChildMeasureSpec(int spec, int padding, int childDimension)来生成一个 measureSpec提供给子控件,getChildMeasureSpec三个参数:spec为父控件 的的测量规则,padding=>父控件的padding,childDimension:子控件的尺寸大小(包括match_parent(-1),warp_content(-2)和大于0的值); 测量出孩子的大小后,我们就可以通过自身的业务需求和孩子的大小来确定自身的大小。

问题1:为何最底层的View不使用自己的LayoutParams来生成尺寸宽高?。当子控件为warp_content而父控件时match_parent时候,父控件说我的宽高就是包裹我的子控件,我的子控件多大我就多大,而子控件说我的宽高填充父窗体,父控件多大我就多大。最后该怎么确定大小呢?所以我们需要为子控件重新生成一个新的约束规则。只要记住,子控件的宽高约束规则是父控件调用getChildMeasureSpec方法生成。 为了流程上的统一,子控件一律采用父控件提供的测量规则来确定自己的尺寸。

getChildMeasureSpec源码如下,三个参数 spec为父亲给该viewGroup的spec,padding,childDimension为孩子尺寸,可能为-1,-2和大于0的值,分别三种情况给child生成spec。

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