android 代码布局设置wrap_content,android 为何设置view为wrap_content但是实际布局大小为match_parent...
描述当我们在设置view的宽高的时候,有时候我们明明设置的是wrap_content,但是却得到了match_parent的效果,特别是我们自定义view的时候,经常碰到这种情况。之前都觉得很神奇,后边慢慢看view的绘制流程以及看源码总算是弄明白原因了。总的来说是在view.measure方法里调用了onMeasure方法,然后onMeasure方法又调用了setMeasureDimension
描述
当我们在设置view的宽高的时候,有时候我们明明设置的是wrap_content,但是却得到了match_parent的效果,特别是我们自定义view的时候,经常碰到这种情况。之前都觉得很神奇,后边慢慢看view的绘制流程以及看源码总算是弄明白原因了。
总的来说是在view.measure方法里调用了onMeasure方法,然后onMeasure方法又调用了setMeasureDimension方法,
此时setMeasureDimension方法传入的参数是通过getDefaultSize方法获取到的精确的尺寸大小,而getDefaultSize(size,measureSpec)
传入的参数size是getSuggestedMinimumWidth的返回值大小(如果背景宽度为null则返回minWidth,不为null则获取max(mBackground.getMiniwidth,minWidth)中一个比较大的值
传入的参数measureSpec为child.measure(widthSpec,heightSpec)的参数
getDefaultSize的具体实现是
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch(specMode){
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
会发现当我们设置view或者viewgroup的宽高为wrap_content的时候,默认就是match_parent的效果
原因就在getDefaultSize里google工程师默认的把AT_MOST和EXACTLY做相同的处理了。
解决
那么如何解决view的wrap_content的情况呢?直接调用setMeasureDimension()传入一个默认的size
那么如何解决viewGroup的wrap_content的情况呢?默认的viewGroup的measure方法里有一句super.onMeasure(WidthMeasureSpec,heightMeasureSpec);
这时候我们需要在super之前人为的重新生成一个measureSpec,替换viewGroup原来的measureSpec
那么如何生成一个measureSpec呢?
通过MeasureSpec.makeMeasureSpec(size,mode)来生成,聪明的你肯定
会问那么这里传入的size和mode值是多少呢?
如果说我们想要获取当前view的wrap_content大小,则通过MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED)来生成。
面试题:我们都知道Scrollview嵌套listview高度会出现问题,而且只会显示一个item的高度,为什么会这样呢?
解答:解决这个问题的话,我们都知道重写listview的onMeasure方法就好,代码如下
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 解决显示不全的问题,具体在listview的源码里面
// 为何要右移两位,因为Integer.MAC_VALUE是一个32位的值,只有加上测量模式才能拼成32位的值
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
我们都知道MeasureSpec是int类型的32位的数,前两位代表规格也就是测量模式(EXACTLY,AT_MOST,UNSPECIFIED)这三种,后30位表示的是大小也就是尺寸,int是32位的,所以要将Integer.MAX_VALUE右移2位,然后拼接测量模式。第二个疑问是为何要是Integer.MAX_VALUE这个数,其他数不行吗?解答如下
listview的源码
...
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
...
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
...
if (returnedHeight >= maxHeight) {
// We went over, figure out which height to return. If returnedHeight > maxHeight,
// then the i'th position did not fit completely.
return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
&& (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // i'th child did not fit completely
? prevHeightWithoutPartialChild
: maxHeight;
}
...
return returnedHeight;
}
上面的maxHeight就是我们设置的Integer.MAX_VALUE>>2,只有设置一个尽量大的值才不会让returnedHeight >= maxHeight,这样测量出来的结果才会正确。
这篇文章分析的更加详细
更多推荐
所有评论(0)