在现实生活中,如果我们要去画一个图形,必须先知道他的大小和位置,同样,在Android中,在绘制一个View前,也必须要先去测量将要绘制的View的大小,这个测量过程在onMeasure()方法中进行。
Android系统给我们提供了一个强大的类MeasureSpec,通过这个类,可以帮助我们测量测量View,MeasureSpec是一个32位的int值,其中高2位代表测量的模式,低30位代表测量的大小。
所以说MeasureSpec类中包含View测量的模式和大小。
那么什么事测量的模式,什么是测量的大小呢?
测量有三种模式
EXACTLY (精确值模式): 当我们测量的控件的layout_width属性或者layout_height属性指定为具体的数值,比如android:layout_width="200dp"
,或者指定为match_parent,比如android:layout_height="match_parent"
时,系统使用EXACTLY模式。
AT_MOST(最大值模式):当我们测量的控件的layout_width属性或者layout_height属性指定为wrap_content时,即控件的大小一般随着其子空间或者内容的内容的变化而变化,此时控件的大小只要不超过父控件所允许的最大尺寸即可。
UNSPECIFIED (未指定模式):它未指定控件的大小测量模式,View想多大就多大,通常像ScrollView中这种类型控件使用这种模式。
onMeasure方法默认只支持EXACTLY模式,所以当我们自定义控件的时候,如果不去重写OnMeasure方法,就只能使用EXACTLY模式,控件可以响应你指定的具体的宽高值,或者match_parent属性。 如果你想要你的自定义View支持wrap_content属性,就必须要重写onMeasure()方法来制定wrap_content时的大小。
通过Meassure类,我们就获取了View的测量模式和View想要绘制的大小,有了这些信息,我们就可以控制View最后显示的大小。
我们写一个类继承View,重写它的onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
我们点击进入**super.onMeasure()**方法中,查看其源码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以发现其内部使用了**setMeasuredDimension(int measuredWidth, int measuredHeight)**方法将最终测量得到的宽高设置进去,从而完成测量的工作。所以,当我们在自定义控件时,通过重写onMeasure()方法,最终就是把测量后的结果作为参数设置给setMeasuredDimension(int measuredWidth, int measuredHeight)方法。
我们把自己定义控件的onMeasure方法进行重写:
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidthSize = measureWidth(widthMeasureSpec);
int measureHeightSize = measureHeight(heightMeasureSpec);
setMeasuredDimension(measureWidthSize, measureHeightSize); // 设置测量结果
private int measureWidth(int measureSpace) {
// 获取测量模式
int mode = MeasureSpec.getMode(measureSpace);
// 获取测量的大小
int size = MeasureSpec.getSize(measureSpace);
if(mode == MeasureSpec.EXACTLY){ // 如果测量模式为精确测量,则直接返回测量的大小
return size;
} else {
int maxSize = 200;
if(mode == MeasureSpec.AT_MOST){ // 如果是至多测量,
return Math.min(maxSize, size); // 设置View的大小最大不能超过200
}
}
return 0;
}
其中measureHeight()方法和measureWidth()方法内容一样。
现在我们在布局文件中设置这个使用这个自定义控件。
先给一个固定的值:
<com.itdujun.custom_view.CustomerView
android:layout_width="400px"
android:layout_height="400px"
android:background="@color/colorAccent"/>
运行会查看结果:
然后在设置为match_parent:
<com.itdujun.custom_view.CustomerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"/>
设置为wrap_content
<com.itdujun.custom_view.CustomerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"/>
因为我们在onMeasure()方法中,代码为如果测量模式为AT_MOST时,起最小尺寸为200px,所以这里显示200px的大小。