Bootstrap

自定义View时wrap_content属性无效的处理方法

最近在学习自定义View,在这里对学到的一些东西做个记录,来加深一下记忆。

首先看View的onMeasure方法

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

这里面有主要有两个方法其中setMeasuredDimension方法是设置View的宽高的,而使wrap_content属性无效的原因要看第二个方法就是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;
    }

在这里还要说一下getDefaultSize传入的两个参数getSuggestedMinimumWidth(), widthMeasureSpec,这里了解一下宽的,高是一样的。

其中getSuggestedMinimumWidth方法源码如下:

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

意思大概就是如果View没背景就返回mMinWidth,如果View设置了背景就返回mMinWidth和mBackground的最小宽度之间最大的那一个。

widthMeasureSpec则是一个32位的int值,它的高2位代表了SpecMode,低30位代表了SpecSize;其中SpecMode指的是测量模式,SpecSize指的是测量大小,而SpecMode只有三种模式分别是UNSPECIFIED、AT_MOST和EXACTLY。这种模式的大致含义如下所示:

  • UNSPECIFIED 未指定模式,View想多大就多大,父容器不做限制。
  • AT_MOST 最大模式,差不多对应wrap_content属性。
  • EXACTLY 精确模式,对应match_parent属性和具体的数值。

接下来在回过头看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://未指定模式下直接返回传进来的的size值
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY://AT_MOST和EXACTLY模式下都返回获取到的specSize 值
            result = specSize;
            break;
        }
        return result;
    }

可以看到AT_MOST和EXACTLY模式都返回获取到的specSize 值,即View在这两种模式下宽高都取决于specSize。由此可以知道当我们直接继承View来自定义View时,对与我们的这个自定义的View来说wrap_content和match_parent属性的效果是相同的。因此当我们直接继承View来自定义View时一定要重写onMeasure方法,来针对wrap_content属性进行处理。例如可以在onMeasure方法中给wrap_content属性指定一个默认的值,代码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取宽高的测量模式
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        //获取宽高的测量大小
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST & heightSpecMode == MeasureSpec.AT_MOST) {//当宽高的测量模式都为AT_MOST时指定默认宽高900
            setMeasuredDimension(900, 900);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {//当宽的测量模式都为AT_MOST时指定默认宽900
            setMeasuredDimension(900, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {//当高的测量模式都为AT_MOST时指定默认高900
            setMeasuredDimension(widthSpecSize, 900);
        }
    }

参考自https://www.jianshu.com/p/6b7e5d2d51ab

;