Android 自定义View onMeasure方法的实现

发觉自己在进行Android开发的时候发觉自己开发的APP的UI几乎是由第三方View库(比如 android-pulltorefresh,ActionBar-PullToRefresh)等等堆砌出来的,发觉自己居然没有自定义过一个控件。心好痛:)

本文是一篇对于Android自定义View时继承View 并实现 onMeasure() 方法的理解。

为什么要写这篇文章

  1. 为了避免以后自定义View的时候掉坑。

  2. 我在国内搜了相关文章(=_=!英语实在拙计),居然发觉理解起来非常生硬,相同的文章也特别多(没错我就是在吐槽天朝的技术贴,尼玛一搜前几篇帖子都是一模一样)

  3. 帮助自己理解以及游客们0.0

问题

  1. 为什麼要实现onMeasure()。

  2. 什么情况下需要实现onMeasure()。

  3. 实现onMeasure()有什么作用。

调用父类onMeasure()

首先定义一个类继承View,重写onMeasure(),并调用父类onMeasure()方法。

public class MeasureExampleView extends View { 
    public MeasureExampleView(Context context) {
        super(context);
    }
    public MeasureExampleView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

再定义layout文件,使用MeasureExampleView并设置background,margin与父容器区分。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
<mzzo.customview.MeasureExampleView
    android:background="@android:color/holo_blue_dark"
    android:layout_margin="10dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
</LinearLayout>

产生效果如下,不难发现MeasureExampleView充满了父控件

修改layout文件中的MeasureExampleView layout_width以及layout_height属性修改成wrap_content,结果仍然是一样的,就不贴图了。

修改layout文件中LinearLayout(父容器) layout_width以及layout_height属性修改成固定的值比如50dp,产生效果如下。发现 MeasureExampleView 跟随了LinearLayout(父容器)设置的固定大小(50dpx50dp)。

修改layout文件中MeasureExampleView layout_width以及layout_height属性修改成固定的值比如50dp,产生效果如下,发现 MeasureExampleView 的大小为设定的值 50dp x 50dp

结论

重写onMeasure(),并调用父类onMeasure()时

  1. MeasureExampleView的layout_width以及layout_height属性值 match_parent 或者 wrap_content显示大小由其父容器控件决定。

  2. MeasureExampleView设置为固定的值,就显示该设定的值

怎样重写onMeasure()

示例

//widthMeasureSpec 和 heightMeasureSpec的值 由父容器决定
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width  = measureDimension(DEFAULT_WIDTH, widthMeasureSpec);
    int height = measureDimension(DEFAULT_HEIGHT, heightMeasureSpec);
    setMeasuredDimension(width, height);
}

定义一个方法处理  widthMeasureSpec,heightMeasureSpec的值

private int measureHanlder(int measureSpec){
    int result = defaultSize;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    if (specMode == MeasureSpec.EXACTLY) {
        result = specSize;
    } else if (specMode == MeasureSpec.AT_MOST) {
        result = Math.min(defaultSize, specSize);
    } else {
        result = defaultSize;
    }
    return result;
}

说明

MeasureSpec.getSize()会解析MeasureSpec值得到父容器width或者height。

MeasureSpec.getMode()会得到三个int类型的值分别为:MeasureSpec.EXACTLY MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED。

MeasureSpec.UNSPECIFIED 未指定,所以可以设置任意大小。

MeasureSpec.AT_MOST  MeasureExampleView可以为任意大小,但是有一个上限。比如这种情况

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <mzzo.customview.MeasureView
        android:background="@android:color/holo_blue_dark"
        android:layout_margin="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MeasureSpec.EXACTLY 父容器为MeasureExampleView决定了一个大小,MeasureExampleView大小只能在这个父容器限制的范围之内。
比如这种情况:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="50dp"
    android:layout_height="50dp">
    <mzzo.customview.MeasureExampleView
        android:background="@android:color/holo_blue_dark"
        android:layout_margin="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

又或者是这种情况:

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <mzzo.customview.MeasureView
        android:background="@android:color/holo_blue_dark"
        android:layout_margin="10dp"
        android:layout_width="50dp"
        android:layout_height="50dp" />
</LinearLayout>

这样当你使用 android:layout_width|android:layout_height属性为:wrap_content时,MeasureView的大小为默认值 100dpx100dp,你也可以根据自己的规则去重写onMeasure()方法。

小结

  • 重写onMeasure()方法是为了自定义View尺寸的规则

  • 如果你的自定义View的尺寸是根据父控件行为一致,就不需要重写onMeasure()方法