Android 自定义view onMeasure() 方法剖析

 

接着上一篇自定义view 相关的,揭秘 Android Graphics2D 实现动态效果之——invalidate()   内容的介绍,这一篇主要介绍下自定义view 中的 onMeasure()方法的使用。

在介绍前,先简单回顾下自定义view 中的 onDraw()方法,该方法主要是将图形通过Paint画在Canvas上,View 上的所有内容都最终显示在Canvas 对象上,但这仅仅是绘制出来图形,但是想要设置View 的大小要怎么设置呢?是在xml 里给自定义view设置wrap_content或者match_parent?还是给自定义view限制一个精确的宽高值呢?带着这几个问题,不妨和我一起看一看:

一、自定义View宽高如何设置

1、设置wrap_content或者match_parent,view都会占满全屏(很简单,就是直接在onDraw()中绘制图形);

占满全屏

2、布局修改,给view限制一个宽高;

限制高度
限制宽高

3、动态添加view修改宽高

 /*方式一*/
CoordinateView coordinateView = new CoordinateView(this);
coordinateView.setBackgroundColor(getResources().getColor(R.color.albumColorPrimary));
linRoot.addView(coordinateView);

 /*方式二*/
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(500,1500);
CoordinateView customView = new CoordinateView(this);
customView.setBackgroundColor(getResources().getColor(R.color.albumColorPrimary));
customView.setLayoutParams(layoutParams);
linRoot.addView(customView);

两个方式区别是,单位不一样,new LinearLayout.LayoutParams(500,1500);一个是像素单位,一个是dp单位

二、认识onMeasure

(1)一般情况重写onMeasure()方法作用是为了自定义View尺寸的规则,如果你的自定义View的尺寸是根据父控件行为一致,就不需要重写onMeasure()方法 ;
(2)如果不重写onMeasure方法,那么自定义view的尺寸默认就和父控件一样大小,当然也可以在布局文件里面写死宽高,而重写该方法可以根据自己的需求设置自定义view大小;

1、onMeasure (int widthMeasureSpec, int heightMeasureSpec)是view自己的方法

2、onMeasure 方法简单的理解就是是用于测量视图的大小,主要是用来测量自己和内容的来确定宽度和高度

3、onMeasure有两个参数 ( int widthMeasureSpec, int heightMeasureSpec),该参数表示控件可获得的空间以及关于这个空间描述的元数据.

4、widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。

/**
 * Created by ${lxb} on 2019/5/16.
 * 邮箱:207***1410@qq.com
 * TIP:坐标转换 学习
 */

public class CoordinateView extends View {

    public CoordinateView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CoordinateView(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //自定义view 如何设置宽高?
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        //设置默认宽高
        setMeasuredDimension(width, height);
    }

    private int measureWidth(int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        //wrap_content
        if (mode == MeasureSpec.AT_MOST) {  // wrap_content

        } else if (mode == MeasureSpec.EXACTLY) {  // fill_parent 或者精确值
//            size = 500;
        }
        return size;//默认
    }

    private int measureHeight(int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        if (mode == MeasureSpec.AT_MOST) {  // wrap_content

        } else if (mode == MeasureSpec.EXACTLY) { // fill_parent 或者精确值
//            size = 3000;
        }
        return size;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

    }
}

三、MeasureSpec 作用

在测量自定义view的大小之前,我们需要认识一个类MeasureSpec,它封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求  MeasureSpec由size和mode组成。

specMode一共有三种类型,如下所示: 
1. EXACTLY 
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,简单的说(当设置width或height为match_parent时,模式为EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的) 
2. AT_MOST 
表示子视图最多只能是specSize中指定的大小。(当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸)

3. UNSPECIFIED 
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。 

四、案例分析

/**
 * Created by ${lxb} on 2019/5/17.
 * 邮箱:207***1410@qq.com
 * TIP: 自定义view onMeasure() 理解
 */

public class OnMeasureView extends View {
    private static final String TAG = OnMeasureView.class.getSimpleName();
    private Context mContext;
    /*定义一个画笔*/
    private Paint mPaint;
    private final String mText = "自定义view,测试文字内容";
    //    private final String mText = "韬睿科技,移动互联网卓越品牌!我爱 Android!";
    /*绘制时控制文本绘制的范围*/
    private Rect mBound;
    private int defaultWidth;
    private int defaultHeight;

    public OnMeasureView(Context context) {
        this(context, null);
    }

    public OnMeasureView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OnMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        mBound = new Rect();
        mPaint = new Paint();
        mPaint.setTextSize(40);
        mPaint.getTextBounds(mText, 0, mText.length(), mBound);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.GRAY);
        mPaint.setColor(Color.RED);
        //绘制文字
        canvas.drawText(
                mText,
                getWidth() / 2 - mBound.width() / 2,
                getHeight() / 2 + mBound.height() / 2,
                mPaint);
    }


    /**
     * 比onDraw() 先执行
     * 1.基准点是baseline
     * 2.ascent:是baseline之上至字符最高处的距离
     * 3.descent:是baseline之下至字符最低处的距离
     * 4.leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
     * 5.top:是指的是最高字符到baseline的值,即ascent的最大值
     * 6.bottom:是指最低字符到baseline的值,即descent的最大值
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int suggestedMinimumWidth = getSuggestedMinimumWidth();//最低限度宽度
        int suggestedMinimumHeight = getSuggestedMinimumHeight();//最低限度高度
        Log.d(TAG, "suggestedMinimumWidth:" + suggestedMinimumWidth);
        Log.d(TAG, "suggestedMinimumHeight:" + suggestedMinimumHeight);
        //设置测量宽高
        int width = measureWith(suggestedMinimumWidth, widthMeasureSpec);
        int height = measureHeight(suggestedMinimumHeight, heightMeasureSpec);
        setMeasuredDimension(width, height);

    }

    private int measureWith(int suggestedMinimumWidth, int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        Log.d(TAG, "size:" + size);
        switch (mode) {
            case MeasureSpec.AT_MOST:
                defaultWidth = (int) mPaint.measureText(mText) + getPaddingLeft() + getPaddingRight();
                break;
            case MeasureSpec.EXACTLY:
                defaultWidth = size;
                break;
            case MeasureSpec.UNSPECIFIED:
                defaultWidth = Math.max(defaultWidth, size);
                break;
        }
        return defaultWidth;

    }

    private int measureHeight(int suggestedMinimumHeight, int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case MeasureSpec.AT_MOST:
                defaultHeight = (int) (-mPaint.ascent() + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
                break;
            case MeasureSpec.EXACTLY:
                defaultHeight = size;
                break;
            case MeasureSpec.UNSPECIFIED:
                defaultHeight = Math.max(defaultHeight, size);
                break;
        }
        return defaultHeight;
    }
}

 改变 OnMeasureView 的宽高对比就很清晰了:

                                                                        图一

                                                                        图二 

                                                                        图三 

知识拓展 ——baseLine 基线

上面的案例中,在onMeasure 注释中提到了,绘制文字的基准线 baseLine,那什么是基线呢?说白了就是一条直线,我们这里理解的是确定它的位置。我们先来看一下基线: 

9

 

 

从上图看出:基线等同四线格的第三条线,在Android中基线的位置定了,那么文字的位置也就定了。

更多自定义view学习,可阅读以下链接:

Android自定义View教程  https://www.gcssloop.com/category/customview

自定义View之绘图篇(三):文字(Text) https://blog.csdn.net/u012551350/article/details/51330308

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值