在绘制View之前,我们还需要考虑一下,系统是如何绘制这些View的。相信大家都曾经玩过这样一个游戏:一个人蒙着眼睛,那笔去画板上画出一个指定的图案,另一个人则通过说话来指导他如何去画。比如你会指导他,在距画板边缘一掌宽的地方画一个边长大概10厘米的正方形,而如果你只告诉他,画一个矩形,那么你的同伴就无法准确的画出这个图形了。其实,Android就好像那么蒙着眼睛画画的人,你必须精确的告诉他该如何去画,它才能绘制出你想要的图形。
在显示生活中,如果我们想要去画一个图形,就必须知道它的大小和位置。同样Android系统在绘制View前,也必须对View进行测量,即告诉系统画出多大的View。这个过程在onMeasure()方法中进行。
Android系统给我们提供了一个设计短小精悍却功能强大的类——MeasureSpec类,通过它来帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。
测量的模式可以为以下三种。
● EXACTLY
即精准值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数字时,比如android:layout_width="100dp",或者指定为match_parent属性时,(占据父View的大小),系统使用的事EXACTLY模式。
● AT_MOST
即最大值模式,当控件的layout_width属性或者layout_height属性指定为wrap_content时,控件大小一般随着控件的子空间或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。
● UNSPECIFIED
这个属性比较奇怪——它不指定其大小测量模式,View想多大就多大,通常情况下在绘制自定义View时才会使用。
View类默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasurs方法的话,就只能使用EXACTLY模式。控件可以响应你指定的具体宽高值或者是match_parent属性。而如果要让自定义View支持wrap_content属性,那么就必须重写onMeasurs()方法来指定wrap_content是的大小。
通过MeasureSpec这一个类,我们就获取了View的测量模式和View想要绘制的大小。有了这些信息,我们就可以控制View最后显示的大小。
下面我们就来看一个简单的实例,演示如何进行View的测量。首先,要重写onMeasue()方法,该方法如下所示。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在IDE中按住Ctrl键查看super.onMeasure()方法。可以发现,系统最终会调用setMeasuredDinmension(int measuredWidth,int measuredHeight)方法将测量后的宽高值设置进去,从而完成测量工作。所以在重写onMeasure()方法后,最终要做的工作就是把测量后的宽高值作为参数传给setMeasuredDimension()方法。
通过上面的分析,重写onMeasure()方法代码如下所示。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeigth(heightMeasureSpec));
}
在onMeasure()方法中,我们调用了measureWidth()方法和measureHeigth()方法,分布对宽高进行重新定义,参数则是宽和高MeasureSpec对象,MeasureSpec对象中包含了测量的模式和测量值的大小。
下面我们就以measureWidth()方法为例,讲解如何自定义测量值。
第一步,从MeasureSpec对象中提取出具体的测量模式和大小,代码如此所示。
int specMode = MeasureSpec.getMode(measureSpec);
int specSizw = MeasureSpec.getSize(measureSpec);
接下来,通过判断测量的模式,给出不同的测量值,当specMode为EXACTLY时,直接使用指定的specSize即可,当specMode为其他两种模式时,需要给它一个默认的大小。特别地,如果指定wrap_content属性,即AT_MOST模式,则需要取出我们指定的大小与specSize中最小的一个来作为最后的测量值,measureWidth()方法的代码如下所示。这段代码基本上也可以作为模板代码。
private int measureWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSizw = MeasureSpec.getSize(measureSpec);
if (specMode==MeasureSpec.EXACTLY){
result = specSizw;
}else {
result = 200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSizw);
}
}
return result;
}
measureHeigth()方法与measureWidth()基本一致,这里就不在给出代码了,通过这两个方法,我们就完成了对宽高值的自定义。最后,可以在程序中验证以上的分析。
当布局文件中,先指定确定的宽高值400px,程序运行效果如图(1)所示。
当指定宽高属性为match_parent属性时,程序运行效果如图(2)所示。
当指定宽高属性为wrap_content属性时,如果不重写onMeasure()方法,那么系统就不知道该使用默认多大的尺寸。因此,它就会默认填充整个父布局,所以重写onMeasure()方法的目的,就是为了能够给View一个wrap_content属性下的默认大小,程序运行效果如图(3)所示。
(1)指定宽高值为400px (2)宽高属性为match_parnet (3)宽高属性为wrap_content
可以发现,当指定wrap_content属性时,View就获得了一个默认值200px,而不是在填充父布局了。
通过这个小的实例,相信大家应该对View的测量不在陌生了,它并没有什么高深莫测的东西,它的整个过程与我们生活中精准绘图过程基本是一样的。