在自定义View的时候,我们第一步往往是进行测量,重写onMeaure()的方法,至于为什么要重写它和怎么重写它,我在刚开始学的时候都是按照书上和博客上或者视频上照着葫芦画瓢,而且书上(疯狂Android讲义)也说的不清不楚,导致我一直没能理解它。在后来开发过程中多次用到onMeaure()的方法后,才开始有点理解。下面我按照我的理解写一下笔记。
我们自定义View总是要显示在屏幕上的对吧?那么显示多大呢?这是由两个东西来共同决定:
- 自定义View的LayoutParams
- 父容器所施加的规则
什么是 — 自定义View的LayoutParams?
就是你在XML文件里面加上你自定义View的layout_width和layout_heigh的参数,如下所示:
<com.zjy.example.mView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
我们用到的就三种参数对吧:
- match_parent
- wrap_content
- 自己给定一个确切的值
什么是 — 父容器所施加的规则?
不管是父容器还是自定义View自己,都会有规则,这个规则有三个:
- UNSPECIFIED:父容器不对自定义View有任何限制
- EXACTLY:父容器已经检测出自定义View的大小,最后自定义View的大小就是这个大小
- AT_MOST:父容器给出一个大小,自定义View不能够大于此值
自定义View到底显示多大呢?
有了这“自定义View的LayoutParams”和“父容器所施加的规则”两个东西,如何决定自定义View的在屏幕的大小?
我们来看看onMeaure()是怎么做的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
···//一系列操作
setMeasuredDimension(width, width);//最后设置宽高的测量值
}
onMeaure() 的两个参数是名字里面都有MeasureSpec这一个单词,其实MeasureSpec是View的一个静态内部类,我们在View里面就可以看到它的源码(我把不重要的删掉了,只看看它的具体作用):
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int getMode(int measureSpec) {}
public static int getSize(int measureSpec) {}
}
从代码看出它其实代表一个32位的int值,高2位代表上面所介绍的3种规则,第30位代表大小。比如AT_MOST就是“10”,在MeasureSpec 里面呢就是“10”右移30位,这样就代表高32位中的高2位是代表规则,专业术语是“SpecMode”。低30位代表大小(在高2位的SpecMode规则下的大小),叫“SpecSize”。
所以onMeaure() 的两个参数widthMeasureSpec, heightMeasureSpec也包含了SpecMode和SpecSize。
widthMeasureSpec, heightMeasureSpec是父容器对自定义View提出的水平和垂直的空间要求。
系统内部将会对“自定义View的LayoutParams”在“父容器所施加的规则”下转换成MeasureSpec ,然后在根据MeasureSpec 来确定View测量后的宽高
刚才说了,MeasureSpec 是View的一个静态内部类,应为我们自定义View肯定是要继承View的,所以我们可以用MeasureSpec 的两个方法来获取SpecMode和SpecSize:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int highMode = MeasureSpec.getMode(heightMeasureSpec);
int highSize = MeasureSpec.getSize(heightMeasureSpec);
我们取得SpecMode和SpecSize之后,就可以按照我们自己的要求来设置自定义View的大小了,比如简单的我们想自定义View是以正方形显示的,取正方形的宽度为widthSize和highSize最小值,再调用setMeasuredDimension()函数进行设置自定义View的宽高的测量值就可以了,当然这一切步骤都是在onMeasure()函数里面完成的。
int width = Math.min(widthSize, highSize);
setMeasuredDimension(width, width);//最后设置宽高的测量值
异常情况
我们会好奇了,widthMode和highMode我们提取出来干嘛的呢?因为自定义View会出现一些异常情况,有时候“自定义View的LayoutParams”设置为“wrap_content”的时候,它在显示的时候却不是“wrap_content”的。这时候提取出widthMode和highMode就派上用处了。可以如下设置:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int highMode = MeasureSpec.getMode(heightMeasureSpec);
int highSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && highMode == MeasureSpec.AT_MOST){
//最后设置宽高的测量值,mWidth和mHeigth是我们自己给定的默认宽高
setMeasuredDimension(mWidth, mHeigth);
}else if (highMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSize , mHeigth);
}else if (widthMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, highSize);
}
}
为什么这么设置呢?建议去看看《Android开发艺术探索》的第4章~~~那是本好书~
参考:
Android开发艺术探索-任玉刚