View的测量

来源:互联网 发布:python exit(1) 编辑:程序博客网 时间:2024/05/18 03:47

      在现实生活中,如果我们要去画一个图形,就必须知道它的大小和位置。同样,Android系统在绘制View之前,也必须对View进行测量,即告诉系统该画一个多大的View。这个过程在onMeasure()方法中进行。

      Android系统给我们提供了一个设计短小精悍却功能强大的类--MeasureSpec类,通过它来帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量模式,低30位为测量的大小,在计算中使用位运算是为了提高效率。

      测量的模式可以为以下三种:

  • EXACTLY

          即精准值模式,当我们将控件的layout_with属性或layout_height属性指定为具体数值时,比如100dp,或制定为match_parent,系统使用的是EXACTLY模式。

  • AT_MOST

          即最大值模式,当我们将控件的layout_with属性或layout_height属性指定为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。

  • UNSPECIFIED

          这个属性比较奇怪---它不知道其大小测量模式,View想多大就多大,通常情况下绘制自定义View时才会使用。

       View类默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasure()方法的话,就只能使用EXACTLY模式。控件可以响应你指定的具体宽高值或者是match_parent属性。而如果要让自定义控件支持wrap_content属性,那么久必须重写onMeasure()方法来指定wrap_content时的大小

       通过MeasureSpec这个类,我们就获取了View的测量模式和View想要绘制的大小。有了这些信息,我们就可以控制View最后显示的大小了。

      下面看一个简单的实例,自定义一个控件,重写onMeasure()方法。

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

      跟踪到super.onMeasure()方法可以看到,系统会最终会调用setMeasuredDimension()方法将测量后的宽高度设置进去,从而完成测量工作。所以再重写onMeasure()方法后,最终要做的工作就是把测量后的宽高度值作为参数设置给setMeasuredDimension()方法。

      下面时测试的例子:

public class MyTextView extends android.support.v7.widget.AppCompatTextView {    public MyTextView(Context context) {        super(context);    }    public MyTextView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));    }    private int measureWidth(int widthMeasureSpec){        int result = 0;        int mode = MeasureSpec.getMode(widthMeasureSpec);        int size = MeasureSpec.getSize(widthMeasureSpec);        if(mode == MeasureSpec.EXACTLY){            result = size;        } else {            result = 200;            if(mode == MeasureSpec.AT_MOST){                result = Math.min(result, size);            }        }        return result;    }    private int measureHeight(int heightMeasureSpec){        int result = 0;        int mode = MeasureSpec.getMode(heightMeasureSpec);        int size = MeasureSpec.getSize(heightMeasureSpec);        if(mode == MeasureSpec.EXACTLY){            result = size;        } else {            result = 200;            if(mode == MeasureSpec.AT_MOST){                result = Math.min(result, size);            }        }        return result;    }}
<?xml version="1.0" encoding="utf-8"?><LinearLayout 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="test.chenj.study_3_1.MainActivity">    <test.chenj.study_3_1.MyTextView        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="#ff666666"        android:text="Hello World!"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintRight_toRightOf="parent"        app:layout_constraintTop_toTopOf="parent" /></LinearLayout>

      下图时分别设置宽和高为wrap_content,200dp,match_parent时的程序运行效果:




      可以发现,当指定wrap_content属性时,View就获得了一个默认的200px,而不是填充父布局了。