Android自定义View学习(二)---重写OnMeasur

作者: 卡咔喀 | 来源:发表于2017-07-27 14:11 被阅读701次

    为什么要重写OnMeasure

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

    <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content" />
    

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

        <com.example.ast.customview.MyTextView
            android:layout_width="250dp"
            android:layout_height="100dp"/>
    

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

    match_parent 宽250 高100

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

    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的大小就是字体的大小了:

    END

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

    相关文章

      网友评论

        本文标题:Android自定义View学习(二)---重写OnMeasur

        本文链接:https://www.haomeiwen.com/subject/kizakxtx.html