描述

当我们在设置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,这样测量出来的结果才会正确。

这篇文章分析的更加详细

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐