android 为何设置view为wrap_content但是实际布局大小为match_parent

描述

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

这篇文章分析的更加详细

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值