最近研究自定义view,遇到一个问题,在自定义ViewGroup的时候经常会在父view的onMeasure方法中调用如下代码去测量子view的宽高:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量ViewGroup本身的宽高,由于没有wrap_content的需求,所以直接用父view的没问题
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量第一个孩子
firstView = getChildAt(0);
//测量第一个孩子的宽高
//方式一:
firstView.measure(0, 0);
//方式二:
firstView.measure(300, heightMeasureSpec);
//方式三:
firstView.measure(MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY));
firstView.measure(1073742124, heightMeasureSpec);
firstView.measure(-2147483648 + 300, heightMeasureSpec);
}
下面对几种方式做详细说明以及自我理解(不保证完全正确...自己写了Demo测试过,应该没错):
当然,首先要对view的绘制流程measure(),layout(),draw()比较了解,MeasureSpec以及三种测量模式熟悉
推荐两篇写的不错的博客:
http://blog.csdn.net/iispring/article/details/49403315
http://blog.csdn.net/lfdfhl/article/details/51347818
下面代码都是在系统默认的实现下进行,没有进行重写!!!
方式一:firstView.measure(0, 0);
其实在measure()方法中最终会回调onMeasure(int widthMeasureSpec, int heightMeasureSpec),所以相当于
widthMeasureSpec = 0;
heightMeasureSpec = 0;
所以当用下面方法获取父view传递的测量模式和测量大小时:
childMode = MeasureSpec.getMode(0) = 0; //获取widthMeasureSpec的最高2位,通过位运算
childWidth = MeasureSpec.getSize(0) = 0; //获取widthMeasureSpec的低30位,通过位运算
当childMode = 0 的时候,也就子view的宽度测量模式是MeasureSpec.UNSPECIFIED,
意思就是父view不限制子view的宽高,子view想要多大就多大,也就是完全wrap_content的效果(没有父view大小的限制),
所以子view布局文件中写的多少宽高,这个并不生效,而是要看具体子view的wrap_content实现(没有父view大小的限制的情况下)
方式二:firstView.measure(300, heightMeasureSpec);
对于宽度:因为传入的是300,由于300转换为32位(int类型是32位)二进制之后,最高2位仍然是00,
所以childMode仍然为0,所以子view的宽度测量模式是MeasureSpec.UNSPECIFIED
最终测量出来的宽度也是子view的wrap_content(没有父view大小的限制)的大小,并不是300
对于高度:因为传入的是父view生成的heightMeasureSpec,所以会根据这个值生成对应的高度
MeasureSpec.UNSPECIFIED;最终高度为子view的wrap_content(没有父view大小的限制)的大小
MeasureSpec.EXACTLY;最终高度为MeasureSpec.getSize(heightMeasureSpec)的值
MeasureSpec.AT_MOST:最终高度不超过MeasureSpec.getSize(heightMeasureSpec)的
值,具体要看子view onMeasure()的实现(有父view大小的限制)
方式三:firstView.measure(MeasureSpec.makeMeasureSpec(300,MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY));
由于子view宽高的测量规格是通过MeasureSpec.makeMeasureSpec()生成的,所以32位的前
两位会是对应的测量模式,子view会在onMeasure()方法中根据大小和模式生成最终的值
总结:假设我们在布局文件中写的宽和高的值都是是500,但是子view的wrap_content的测量宽高大小是800,最终结果如下:
方式一:childWidth = childHeight = 800;
方式二:childWidth = 800;
childHeight = 500;
方式三:childWidth = childHeight = 300;
由于Android系统中,下面三个变量转换成十进制之后的值如下所示:
MeasureSpec.UNSPECIFIED = 0;
MeasureSpec.EXACTLY = 1073741824;
MeasureSpec.AT_MOST = -2147483648;
所以在measure()中传入的值X分类如下:
当x > 1073741824的时候,测量模式为MeasureSpec.EXACTLY,测量大小是(X-1073741824);
当0<X<1073741824的时候,测量模式为MeasureSpec.UNSPECIFIED,测量大小是wrap_content具体控件的实现高度;
当-2147483648<X<0的时候,测量模式为MeasureSpec.AT_MOST,测量大小是不能超过(|X| - 2147483648);
当x < -2147483648的时候,测量模式为MeasureSpec.UNSPECIFIED,测量大小是wrap_content具体控件的实现高度(并且没有父view大小的限制);
里面有很多细节的东西没有讲,讲起来牵扯的东西太多,作为自己的笔记吧!有问题的可以指出和一起探讨!
下面附上在系统ViewGroup中用于生成子view的MeasureSpec的源码(非常重要):
在measureChildren()中遍历子view并调用measureChildWithMargins()方法,
在measureChildWithMargins()方法中调用getChildMeasureSpec()生成子view的measureSpec,并测量子view
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}