Android自定义View之——View的测量
View的测量是通过函数measure来完成的,measure函数是由final进行修饰的,也就是说子类是无法重写该方法的。
通过View的源码可以看到,measure里面实际调用了onMeasure方法,该方法是可以被重写的。也就是在自定义View的时候,测量View的尺寸大小通过重写onMeasure方法来完成。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
可以看到onMeasure方法传入了两个int值,我们先来看看这两个int值是干嘛的?
其实这里是通过一个32位的二进制值来存储了布局寬高的大小size值和类型mode,其中,高两位存储的是mode(三种类型,高两位存储足矣),低30位存储的大小值,Android为我们提供了一个类MeasureSpec来获取其中的size和mode
int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取width的size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取width的mode
//下面通过源码看下两个方法,熟悉二进制的移位运算和&运算的不难看出其中的原理如上所述
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
接着看一下onMeasure(int widthMeasureSpec, int heightMeasureSpec)中两个值是如何得来的,
子view的measure方法是父viewgroup调用measure时依次调用的,通常构建出MeasureSpec的函数如下
/*
* spec是来自父布局的MeasureSpec
* padding为childview的内边距
* childDimension则为设置的子view具体的寬高值
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);//获取到父view的mode
int specSize = MeasureSpec.getSize(spec);//获取到父view的size
int size = Math.max(0, specSize - padding);//可绘制的子view的尺寸大小0 ~ specsize - padding
int resultSize = 0;//子view的大小
int resultMode = 0;//子view的mode
switch (specMode) {//根据父view的mode来设置子view的mode和size
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY://父view的尺寸是确定的,match_parent或精确的值
if (childDimension >= 0) {//说明子view的size是确定的dp值
resultSize = childDimension;//大小为子view传进来的size
resultMode = MeasureSpec.EXACTLY;//子view大小是具体的值,mode为EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {//子view尺寸设置为match_parent
// Child wants to be our size. So be it.
resultSize = size;//子view的size为之前由父view计算的最大可绘制大小
resultMode = MeasureSpec.EXACTLY;//子view大小是具体的值,mode为EXACTLY
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//子view尺寸设置为wrap_content
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;//子view的size为之前由父view计算的最大可绘制大小
resultMode = MeasureSpec.AT_MOST;//子view的mode为AT_MOST
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST://父view的大小不能超过其父view的尺寸大小时
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子view设置了dp值,大小还是由dp决定,所以mode还是EXACTLY(确定的)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
//父view的大小不能超过其父view的尺寸大小,其子view设置为match_parent,
//子view的大小为父view的尺寸大小,所以mode仍然为AT_MOST
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
//父view的大小不能超过其父view的尺寸大小,其子view设置为wrap_content,
//结果还是不能超过该子view的父view的尺寸大小,所以mode仍然为AT_MOST
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);//最后将子view的mode和size共同构成一个MeasureSpec,即view中measure传入的值
}
总结一下:
- 当mode为EXACTLY时,表示view的尺寸是精确的值,父view希望子view的尺寸应该是由MeasureSpec来决定的。
即子view设置了dp或者match_parent,而其尺寸大小在父view中已经设置给MeasureSpec,所以在自定义view时,
该view的尺寸可直接设置为MeasureSpec获得的size。 - 当mode为AT_MOST时,则表示该view的尺寸是不确定的,但是不应该超过父view的尺寸大小,即该view设置了wrap_content,
此时需要指定该view可绘制的大小值,但不要超过父view的尺寸大小,当自定义view时需要指定具体的大小值(该大小不应超过MeasureSpec获得的size),而不是使用MeasureSpec获取的大小。 - UNSPECIFIED是指可以将view的尺寸设置为任意大小的值,一般不常使用。