自定义View(二):深入解析onMeasure()

1、onMeasure()方法的作用

在自定义View中有三个非常重要的方法,onMeasure()、layout()、onDraw(),分别负责View的测量、滑动和绘画,首先我来介绍一下onMeasure()的用法。

首先我们来测试一下View的测量规律,新建一个MyView类继承View,并实重写它的四个构造方法,然后再在xml引用一个MyView,分别测试layout_width和layout_hight为match_parent和wrap_content有什么不同。


自定义View(二):深入解析onMeasure()

看看运行结果:

自定义View(二):深入解析onMeasure()


不出所料,MyView确实是沾满了父布局的空间。然后我们再来测试一下wrap_content。

自定义View(二):深入解析onMeasure()

再运行一次我们可以看到MyView也是占满整个父布局的,如果想要自定义wrap_content则必须要重写onMeasure()方法。

2、源码解析

话不多说,上源码

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

在onMeasure()方法里面只调用了一个setMeasuredDimension()方法方法,很显然是用来设置View的宽和高的,宽度和高度都是同样原理,我们先以宽度为例看看getSuggestedMinimumWidth()方法源码

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

可以看到这个方法的大概意思就是如果View没有设置背景则返回mMinWidth,如果有设置背景的话则返回背景的宽度,mMinWidth的值默认为0,可以通过setMinimunWidth()方法来设置。我们把重点放在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;
}

参数size就是上面我所说的getSuggestedMinimumWidth()所返回的值,specSize就是父布局最大的宽度值。然后我先来给大家介绍一下specMode的三种情况分别是什么意思。
UNSPECIFIED:未指定模式,View想多大就多大,不受父布局的控制,一般用于系统内部的测量;

AT_MOST:最大模式,当属性为wrap_content的时候就会被调用,限制了view的最大值为父控件的值,也就是specSize;

EXACTLY:精确模式,当属性为macth_parent的时候就会被调用;

现在可以很清晰知道为什么属性为wrap_content的时候为什么view会占满父控件的所有空间了,所以想要自定义wrap_content情况先View的大小只要重写此方法就可以,由于这个是静态方法不能被重写,我们直接复制改一个方法名字就好了。

3、方法重写

自定义View(二):深入解析onMeasure()

再次运行的时候我们可以发现再也没有出现wrap_content情况下占满全屏,而是它的宽高度都为0了,原因是没有设置背景,getSuggestedMinimumHight()和getSuggestedMinimumWidth()都返回了0,我们可以引用自定义属性来测量View的大小,关于自定义属性的获取,不懂的同学可以看看我上一篇文章。上完整代码

MyView.java

public class MyView extends View{
    private int mhight,mwidth;
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        //自定义属性
        mhight = array.getDimensionPixelSize(R.styleable.MyView_default_hight, 100);
        mwidth = array.getDimensionPixelSize(R.styleable.MyView_default_width, 100);
        array.recycle();  //释放资源
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getMyViewDefaultSize(mwidth, widthMeasureSpec),
                getMyViewDefaultSize(mhight, heightMeasureSpec));
    }

    //重写getDefaultSize
    public int getMyViewDefaultSize(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:
                result=size;
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }
}



activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.measuredemo.MainActivity">

    <com.measuredemo.MyView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:background="@android:color/darker_gray"
        app:default_hight="100dp"
        app:default_width="200dp"
        />
</FrameLayout>


attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <!--name:自定义属性名,format:自定义属性数据类型-->
        <attr name="default_width" format="dimension"></attr>
        <attr name="default_hight" format="dimension"></attr>

    </declare-styleable>
</resources>


运行结果:

自定义View(二):深入解析onMeasure()


4、源码链接

https://github.com/Hasagit/OnMeasureDemo.git