android 自定义view-onMeasure

来源:互联网 发布:java角色权限 编辑:程序博客网 时间:2024/05/16 14:40


当进行自定义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 0
原创粉丝点击