一. 问题描述
在使用自定义View时,View宽 / 高的 wrap_content 属性不起自身应有的作用,而且是起到与 match_parent相同作用。
wrap_content 与 match_parent 区别:
- wrap_content:视图的宽/高被设定成刚好适应视图内容的最小尺寸。
- match_parent:视图的宽/高被设置为充满整个父布局。
其实这里有两个问题:
- 问题1:wrap_content 属性不起自身应有的作用。
- 问题2:wrap_content起到与match_parent相同的作用。
二. 问题分析
问题出现在View的宽 / 高设置,那我们直接来看自定义View绘制中第一步对View宽 / 高设置的过程:measure过程中的 onMeasure 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
public static int getDefaultSize(int size, int measureSpec) {
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; }
return result; }
|
在 getDefaultSize 的默认实现中,当View的测量模式是 AT_MOST 或 EXACTLY 时,View的大小都会被设置成子View MeasureSpec 的 specSize。
因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_content 和 match_parent 是具有相同的效果的。
解决了问题2:wrap_content起到与match_parent相同的作用。
那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?
由于在getDefaultSize()
的默认实现中,当View被设置成 wrap_content 和 match_parent 时,View的大小都会被设置成子View MeasureSpec的specSize。
所以,这个问题1转换成了 子View MeasureSpec的specSize的值是多少?
子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| / 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
* @param spec 父view的详细测量值(MeasureSpec) * @param padding view当前尺寸的的内边距和外边距(padding,margin) * @param 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) { case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break;
case MeasureSpec.AT_MOST: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break;
case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
|

从上面可以看出,当子View的布局参数使用match_parent
或wrap_content
时:
- 子View的specSize(宽 / 高):parenSize = 父容器当前剩余空间大小 = match_content
三. 问题总结
在onMeasure()中的getDefaultSize()
的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。
因为AT_MOST对应wrap_content
;EXACTLY对应match_parent
,所以,默认情况下,wrap_content
和match_parent
是具有相同的效果的。
因为在计算子View MeasureSpec的getChildMeasureSpec()
中,子View MeasureSpec在属性被设置为wrap_content
或match_parent
情况下,子View MeasureSpec的specSize被设置成parenSize = 父容器当前剩余空间大小
所以:wrap_content
起到了和match_parent
相同的作用:等于父容器当前剩余空间大小
四. 解决方案
当自定义View的布局参数设置成wrap_content时时,指定一个默认大小(宽 / 高)。具体是在复写onMeasure()
里进行设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int mWidth = 400; int mHeight = 400;
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(mWidth, mHeight); } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(mWidth, heightSize); } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(widthSize, mHeight); }
|