Android自定义View学习(二)---重写OnMeasure设置View的尺寸

为什么要重写OnMeasure

在我们使用控件时,想要设置View的尺寸,经常会在布局方法中使用 match_parent设置View填充父元素或wrap_content包裹内容:


又或者设定具体的参数值:

    

但我们使用自定义View时,match_parent和指定具体参数控件能按要求绘制:

Android自定义View学习(二)---重写OnMeasure设置View的尺寸_第1张图片
match_parent
Android自定义View学习(二)---重写OnMeasure设置View的尺寸_第2张图片
宽250 高100

但当使用wrap_content时,情况却不对了:

Android自定义View学习(二)---重写OnMeasure设置View的尺寸_第3张图片
wrap_content

为了让wrap_content能够正常工作,我们需要重写onMeasure方法。

出现问题的原因

View的绘制有三种模式:UNSPECIFIED(未指定),EXACTLY(完全)和AT_MOST(至多)。这三种模式分别对应:

模式 布局参数 说明
UNSPECIFIED(未指定) —— 控件尺寸无约束,未见过此情况
EXACTLY(完全) match_parent或具体宽高值 控件尺寸被指定,内容只能在给定空间内绘制
AT_MOST(至多) wrap_content 控件尺寸由内容决定且尽可能大

查看View内的onMeasure源码:

    //onMeasure
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    //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;
    }

可以看到AT_MOST和EXACTLY返回的是相同的specSiz,这也是使用wrap_content和match_parent会出现相同的结果的原因。

如何解决问题

要解决这个问题,就要重写onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);   //获取宽的模式
    int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高的模式
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);   //获取宽的尺寸
    int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的尺寸
    Log.v(TAG, "宽的模式:"+widthMode);
    Log.v(TAG, "高的模式:"+heightMode);
    Log.v(TAG, "宽的尺寸:"+widthSize);
    Log.v(TAG, "高的尺寸:"+heightSize);
    int width;
    int height ;
    if (widthMode == MeasureSpec.EXACTLY) {
        //如果match_parent或者具体的值,直接赋值
        width = widthSize;
    } else {
        //如果是wrap_content,我们要得到控件需要多大的尺寸
        float textWidth = mBound.width();   //文本的宽度
        //控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding值,在构造方法执行完就被赋值
        width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
        Log.v(TAG, "文本的宽度:"+textWidth + "控件的宽度:"+width);
    }
    //高度跟宽度处理方式一样
    if (heightMode == MeasureSpec.EXACTLY) {
        height = heightSize;
    } else {
        float textHeight = mBound.height();
        height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
        Log.v(TAG, "文本的高度:"+textHeight + "控件的高度:"+height);
    }
    //保存测量宽度和测量高度
    setMeasuredDimension(width, height);
}

这样就能解决wrap_content铺满整个屏幕的问题了,但是这只是一个粗糙的方案,只是提供思路,使用这种解决方法并不能和TextView完全一致,当不设置padding时,View的大小就是字体的大小了:

Android自定义View学习(二)---重写OnMeasure设置View的尺寸_第4张图片

END

本文章参考了:
Android自定义View(一、初体验自定义TextView)----open-Xu

你可能感兴趣的:(Android自定义View学习(二)---重写OnMeasure设置View的尺寸)