android 自定义view-onMeasure


当进行自定义view时我们首先需要知道这个view的大小,在android中是通过onMeasure来进行测量的,在看《android群英传》后记录学习过程。

在自定义view时我们需要重写onMeasure方法进行view的测量。

/**
     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overriden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass'
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     *
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     * @see #getMeasuredWidth()
     * @see #getMeasuredHeight()
     * @see #setMeasuredDimension(int, int)
     * @see #getSuggestedMinimumHeight()
     * @see #getSuggestedMinimumWidth()
     * @see android.view.View.MeasureSpec#getMode(int)
     * @see android.view.View.MeasureSpec#getSize(int)
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
以上是父类view的onMeasure方法实现,我们可以看到系统会最终调用setMeasureDimension(width,height)方法将测量后的宽和高设置进去,完成view的测量,所以我们重写view的时候需要把测量的view的宽和高传到 setMeasureDimension里面去完成测量。


如何测量:

android中提供给我们一个MeasureSpec类来完成view的测量。MeasureSpec是一个32位的int 值,其中高2位为测量的模式,低2位为测量的大小。测量的模式主要分为EXACTLY、AT_MOST、UNSPECIFIED三种值。

  • EXACTLY
准确精度值模式,当设置控件的宽和高为具体的值是如:100dp等就是用的这个值,或者match_parent
  • AT_MOST
当设置控件的宽和高为wrap_content时,则其大小为控件的内容变化而变化。
  • UNSPECIFIED
不指定测量大小模式,view想多大就有多大。

view的默认的模式就是第一种精确模式,所以自定义view时不重写onMeasure方法就是使用EXACTLY模式,即支持view的具体值或者match_parent属性,而如果view的宽和高指定为wrap_content时就必须重写onMeasure方法。

下面通过一个具体的实例进行加深理解:
public class TestCustomeView2 extends View {
    public TestCustomeView2(Context context) {
        super(context);
    }

    public TestCustomeView2(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestCustomeView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

}
布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".act.MainActivity">

    <com.customview.widget.TestCustomeView2
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#fffaaa"/>
</RelativeLayout>

我们自定义view,没有重写onMeasure方法,此时布局文件写的宽和高为wrap_content,此时系统不知道该绘制多大的,所致默认会填充整个父布局,我们重写onMeasure方法就是让系统能够知道view的大小从而能够绘制出来。



当我们指定了控件的具体大小或者为match_parent时系统此刻就知道该绘制多大的了。
布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".act.MainActivity">

    <com.customview.widget.TestCustomeView2
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:background="#fffaaa"/>
</RelativeLayout>


重写onMeasure方法获取view的大小:
public class TestCustomeView extends View {
    public TestCustomeView(Context context) {
        super(context);
    }

    public TestCustomeView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestCustomeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }


    /**
     * 获取view的高度,当没有指定大小时给予默认的高为400px
     * @param measureSpec
     * @return
     */
    private int measureHeight(int measureSpec) {
        int result = 0;

        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        //如果布局控件指定大小则返回其大小值,否则返回默认的值
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 400;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;

    }

    /**
     * 获取view的宽度,当没有指定大小时给予默认的高为200px
     * @param measureSpec
     * @return
     */
    private int measureWidth(int measureSpec) {
        int result = 0;

        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        //如果布局控件指定大小则返回其大小值,否则返回默认的值
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
}
布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".act.MainActivity">

    <com.customview.widget.TestCustomeView
        android:layout_width="400dp"
        android:layout_height="200dp"
        android:background="#fffaaa"/>
</RelativeLayout>


若我们没有指定其大小,而是使用wrap_content时则会显示默认的大小200px和400dpx
布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".act.MainActivity">

    <com.customview.widget.TestCustomeView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#fffaaa"/>
</RelativeLayout>



通过以上实例我们就可以加深对onMeasure方法的理解。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值