1.首先明确自定义view测量的目的:确定自身的大小。
首先 onMeasure(int widthMeasureSpec, int heightMeasureSpec);方法时parent给子View的约束条件。里面包含了该子view的mode和建议的大小尺寸。
实际中对于match_parent和精确的数值大小系统已经给我们解决了,一般不需要修改,我们只需处理好warp_content的情况就好了。
1.1 当自定义View继承View.
这个时候只需根据parent view给出的测量规则确定自身大小即可。如下模板所示
/**
* 获取合适的大小
*
* @param defaultSize 默认大小
* @param measureSpec 测量规格
*/
private int getProperSize(int defaultSize, int measureSpec) {
int properSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED:
// 没有指定大小,设置为默认大小
Log.d(TAG,"UNSPECIFIED");
properSize = defaultSize;
break;
case MeasureSpec.EXACTLY:
// 固定大小,无需更改其大小
Log.d(TAG,"EXACTLY");
properSize = size;
break;
case MeasureSpec.AT_MOST:
Log.d(TAG,"AT_MOST");
// 此处该值可以取小于等于最大值的任意值,此处根据实际情况自行确定
properSize = size >defaultSize?defaultSize:size;
}
return properSize;
}
1.2 自定义ViewGroup。
我们想要确定自身的大小,需要知道孩子的大小,ViewGroup提供了measureChild方法.measureChild方法根据父控件的测量规则和子控件的layoutParams来通过方法getChildMeasureSpec(int spec, int padding, int childDimension)来生成一个 measureSpec提供给子控件,getChildMeasureSpec三个参数:spec为父控件 的的测量规则,padding=>父控件的padding,childDimension:子控件的尺寸大小(包括match_parent(-1),warp_content(-2)和大于0的值); 测量出孩子的大小后,我们就可以通过自身的业务需求和孩子的大小来确定自身的大小。
问题1:为何最底层的View不使用自己的LayoutParams来生成尺寸宽高?。当子控件为warp_content而父控件时match_parent时候,父控件说我的宽高就是包裹我的子控件,我的子控件多大我就多大,而子控件说我的宽高填充父窗体,父控件多大我就多大。最后该怎么确定大小呢?所以我们需要为子控件重新生成一个新的约束规则。只要记住,子控件的宽高约束规则是父控件调用getChildMeasureSpec方法生成。 为了流程上的统一,子控件一律采用父控件提供的测量规则来确定自己的尺寸。
getChildMeasureSpec源码如下,三个参数 spec为父亲给该viewGroup的spec,padding,childDimension为孩子尺寸,可能为-1,-2和大于0的值,分别三种情况给child生成spec。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} 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;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.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;
} 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;
}
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);
}