View的onMeasure()方法使用解析

之前在看Android开发艺术探索的时候也有写过一篇AndroidView的measure过程的文章,现在回头看看把自己看的都一头雾水,妥妥的水文,抽空还要再去把书读两遍才行啊。

一、目标

明确MeasureSpec三种测量模式的具体含义,并根据实际需求测量View的大小

二、明确MeasureSpec三种测量模式的含义

  • EXACTLY :父控件已经确定了子控件的大小
  • AT_MOST:父控件对子控件没有约束,但存在上限,上限一般是父控件的大小
  • UNSPECIFIED:父控件对子控件没有任何约束。它可以是任意大小。

为了验证各个模式的含义,我们写个一测试的TestView,只显示一段文字,并打印View的测量模式。

public class TestView extends View {

    private Paint mPaint;
    
    public TestView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    
    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(50);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("测试", 0, 50, mPaint);
    }

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

        String strWidthMode = "";
        String strHeightMode = "";
        //宽度的测量模式
        switch (widthMode) {
            case MeasureSpec.AT_MOST:
                strWidthMode = "AT_MOST";
                break;
            case MeasureSpec.EXACTLY:
                strWidthMode = "EXACTLY";
                break;
            case MeasureSpec.UNSPECIFIED:
                strWidthMode = "UNSPECIFIED";
                break;
        }
        //高度的测量模式
        switch (heightMode) {
            case MeasureSpec.AT_MOST:
                strHeightMode = "AT_MOST";
                break;
            case MeasureSpec.EXACTLY:
                strHeightMode = "EXACTLY";
                break;
            case MeasureSpec.UNSPECIFIED:
                strHeightMode = "UNSPECIFIED";
                break;
        }
        //打印测量模式
        Log.e("tag", "===  WidthMode: " + strWidthMode + "   HeightMode: " + strHeightMode 
                + "   widthSize: " + widthSize + "   heightSize: " + heightSize);
    }
}

XML文件布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="#fff"
              android:orientation="vertical"
    >

    <com.zy.test.cusview.TestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#0a0aea"
        />

</LinearLayout>
1、注意此时父布局宽高是match_parent,TestView是自适应。并且给TestView设置了一个蓝色的背景

运行结果如下:

E/tag: ===  WidthMode: AT_MOST   HeightMode: AT_MOST   widthSize: 800   heightSize: 1199
E/tag: ===  WidthMode: AT_MOST   HeightMode: AT_MOST   widthSize: 800   heightSize: 1199

运行截图截图可以看出:
在这里插入图片描述

从运行结果可以看出,当TestView设置自适应的时候,它的测量模式是AT_MOST。并且此时我们的TestView充满全屏显示。
2、修改父布局的宽高为 wrap_content

在这里插入图片描述
运行结果如下:

E/tag: ===  WidthMode: AT_MOST   HeightMode: AT_MOST   widthSize: 800   heightSize: 1199
E/tag: ===  WidthMode: AT_MOST   HeightMode: AT_MOST   widthSize: 800   heightSize: 1199

当我们TestView和父布局都自适应时,TestView也是全屏显示,并且测量模式为: AT_MOST

3、给TestView设置确定的大小,父控件自适应

在这里插入图片描述
运行结果如下:

 E/tag: ===  WidthMode: EXACTLY   HeightMode: EXACTLY   widthSize: 200   heightSize: 200
 E/tag: ===  WidthMode: EXACTLY   HeightMode: EXACTLY   widthSize: 200   heightSize: 200

截图:
在这里插入图片描述
当我们给TestView的宽高一个确定的大小时,TestView的测量模式为EXACTLY,此时宽高固定。

4、当我们TestView宽高为match_parent时

在这里插入图片描述
运行结果如下:

E/tag: ===  WidthMode: AT_MOST   HeightMode: AT_MOST   widthSize: 800   heightSize: 1199
E/tag: ===  WidthMode: EXACTLY   HeightMode: EXACTLY   widthSize: 800   heightSize: 1199
E/tag: ===  WidthMode: AT_MOST   HeightMode: AT_MOST   widthSize: 800   heightSize: 1199
E/tag: ===  WidthMode: EXACTLY   HeightMode: EXACTLY   widthSize: 800   heightSize: 1199

从运行结果上可以看到,虽然View测量了多次,但最终它的测量模式为EXACTLY,并且TestView全屏显示。

同理当父控件宽高为match_parent时,依然是EXACTLY 模式, 运行结果如下:

E/tag: ===  WidthMode: EXACTLY   HeightMode: EXACTLY   widthSize: 800   heightSize: 1199
E/tag: ===  WidthMode: EXACTLY   HeightMode: EXACTLY   widthSize: 800   heightSize: 1199

对比两次结果我们可以知道当父布局宽高设置为wrap_content自适应时,会进行多次测量。而 宽高设置为match_parent充满全屏,因为屏幕是固定的,所以宽高也是固定的。不会进行多次测量。

ScrollView嵌套TestView时

在这里插入图片描述
执行结果如下:

 E/tag: ===  WidthMode: EXACTLY   HeightMode: UNSPECIFIED   widthSize: 200   heightSize: 0
 E/tag: ===  WidthMode: EXACTLY   HeightMode: UNSPECIFIED   widthSize: 200   heightSize: 0

可以看到此时TestView高德的测量模式为UNSPECIFIED,并且测量高度heightSize为0。

总结上面的测试我们可以得到下面结论

在这里插入图片描述

三、明确了MeasureSpec的三种测量模式后,我们要如何修改,才能达到向TextView一样,显示一段文字呢?

我们可以看一下TextView的onMeasure()方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	//获得宽高的测量模式和测量大小
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;
		.....//省略部分代码
        if (widthMode == MeasureSpec.EXACTLY) {
            // 如果宽度的测量模式是EXACTLY,宽度就是测量的宽度
            width = widthSize;
        } else {
           ......//省略部分代码
            width += getCompoundPaddingLeft() + getCompoundPaddingRight();

           // Check against our minimum width
            width = Math.max(width, getSuggestedMinimumWidth());

            if (widthMode == MeasureSpec.AT_MOST) {
            	//如果宽度的测量模式是AT_MOST实际值
                width = Math.min(widthSize, width);
            }
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            // 如果高度的测量模式是EXACTLY,已经确定大小,则高度为测量的高度
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else {
            int desired = getDesiredHeight();

            height = desired;
            mDesiredHeightAtMeasure = desired;

            if (heightMode == MeasureSpec.AT_MOST) {
            	//如果宽度的测量模式为AT_MOST,取实际高度
                height = Math.min(desired, heightSize);
            }
        }
  		......//省略部分代码
  		//设置宽高
        setMeasuredDimension(width, height);
    }

TextView的功能比较复杂,所以源码也比较复杂。这里只展示了相关的代码。可以看到,TextView也是根据View的测量模式来进行View的宽高进行测量的。

接下来,测量一下我们的TestView的宽高。

根据TextView,修改我们的TestView测量方法
public class TestView extends View {

    private Paint mPaint;
    private String mText = "天青色等烟雨,而我在等你";
    private int mHeight = 50;

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

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(50);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mText, 0, mHeight, mPaint);
    }

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

        int width;
        int height;

        if (widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        }else if (widthMode == MeasureSpec.AT_MOST){
            width = (int)Math.ceil(mPaint.measureText(mText));
        }else {
            width = (int)Math.ceil(mPaint.measureText(mText));
        }

        if (heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        }else if (heightMode == MeasureSpec.AT_MOST){
            height = mHeight;
        }else {
            height = mHeight;
        }
        //设置宽高
        setMeasuredDimension(width,height);
    }
}

测试:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="#fff"
              android:orientation="vertical"
    >

    <com.zy.test.cusview.TestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#0a0aea"
        />

</LinearLayout>

修改之前运行结果:
在这里插入图片描述
在这里插入图片描述
到现在总算知道了该如何测量View的大小。感觉表达的方式还有些问题。可能要等过段时间再回过头重新修改了

部分参考:https://www.cnblogs.com/wjtaigwh/p/6600035.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值