子 View 的测量模式和大小请看下表:
Paste_Image.png
规律总结:(以子 View 为标准,横向观察)
- 当子 View 采用具体数值(dp / px)时
无论父容器的测量模式是什么,子 View 的测量模式都是 EXACTLY 且大小等于设置的具体数值; - 当子 View 采用 match_parent 时
- 子 View 的测量模式与父容器的测量模式一致
- 若测量模式为 EXACTLY,则子 View 的大小为父容器的剩余空间;若测量模式为 AT_MOST,则子 View 的大小不超过父容器的剩余空间
- 当子 View 采用 wrap_parent 时
无论父容器的测量模式是什么,子 View 的测量模式都是 AT_MOST 且大小不超过父容器的剩余空间。
UNSPECIFIED 模式:由于适用于系统内部多次 measure 情况,很少用到,故此处不讨论
问题描述
在使用自定义 View 时,View 宽 / 高的 wrap_content
属性不起自身应有的作用,而且是起到与 match_parent
相同作用。
问题分析
问题出现在 View 的宽 / 高设置,那我们直接来看自定义 View 绘制中第一步对 View 宽 / 高设置的过程:measure 过程中的 onMeasure()
方法
onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//参数说明:View的宽 / 高测量规格
//setMeasuredDimension() 用于获得View宽/高的测量值
//这两个参数是通过getDefaultSize()获得的
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
继续往下看 getDefaultSize()
getDefaultSize()
- 作用:根据 View 宽/高的测量规格计算 View 的宽/高值
- 源码分析如下:
public static int getDefaultSize(int size, int measureSpec) {
//参数说明:
// 第一个参数size:提供的默认大小
// 第二个参数:宽/高的测量规格(含模式 & 测量大小)
//设置默认大小
int result = size;
//获取宽/高测量规格的模式 & 测量大小
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// 模式为UNSPECIFIED时,使用提供的默认大小
// 即第一个参数:size
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值
// 即measureSpec中的specSize
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
//返回View的宽/高值
return result;
}
从上面发现:
- 在
getDefaultSize()
的默认实现中,当 View 的测量模式是 AT_MOST 或 EXACTLY 时,View 的大小都会被设置成子 View MeasureSpec 的 specSize。 - 因为 AT_MOST 对应 wrap_content;EXACTLY 对应 match_parent,所以,默认情况下,
wrap_content
和match_parent
是具有相同的效果的。
解决方案:
当自定义 View 的布局参数设置成 wrap_content 时时,指定一个默认大小(宽 / 高)。
具体是在复写
onMeasure()
里进行设置
@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);
// 设置wrap_content的默认宽 / 高值
// 默认宽/高的设定并无固定依据,根据需要灵活设置
// 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,
//具体读者可以自行查看
int mWidth = 400;
int mHeight = 400;
// 当布局参数设置为wrap_content时,设置默认值
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT &&
getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
// 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, heightSize);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSize, mHeight);
}
这样,当你的自定义 View 的宽 / 高设置成 wrap_content 属性时就会生效了。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于