目录
示意图
1. 问题描述
使用自定义View时,View宽/高的wrap_content属性没有自己的功能,和match_parent起到同样的作用。
wrap_content 和match_parent 的区别:
wrap_content:视图的宽度/高度设置为刚好适合视图内容的最小尺寸。 match_parent:视图的宽度/高度设置为填充整个父布局。
(Android API 8之前称为fill_parent)其实这里有两个问题:
问题1:wrap_content属性没有发挥其应有的作用。问题2:wrap_content与match_parent的作用相同。
2. 知识储备
在分析解决问题之前,请先阅读自定义View原理(二)自定义View测量流程-最易理解的自定义View原理系列
3. 问题分析
视图的宽度/高度设置出现问题。我们直接看自定义View绘制中设置View宽高的第一步:测量过程中的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, intmeasureSpec) {
//参数说明:
//第一个参数size:提供的默认大小
//第二个参数:宽/高测量规格(包括模式测量尺寸)
//设置默认大小
int 结果=大小;
//获取宽高测量规格的模式测量尺寸
int specMode=MeasureSpec.getMode(measureSpec);
int specSize=MeasureSpec.getSize(measureSpec);
开关(规格模式){
//当模式为UNSPECIFIED 时,使用提供的默认大小
//即第一个参数:size
案例MeasureSpec.UNSPECIFIED:
结果=大小;
休息;
//当模式为AT_MOST,EXACTLY时,使用View测量的宽/高值
//即measureSpec中的specSize
案例MeasureSpec.AT_MOST:
案例MeasureSpec.EXACTLY:
结果=规格大小;
休息;
}
//返回View的宽/高值
返回结果;
从上面发现:
在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。因此,这个问题的关键是子View MeasureSpec的specSize的值是多少。我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算出来的。具体的计算逻辑封装在getChildMeasureSpec()中。
接下来我们看一下生成子View MeasureSpec的方法:getChildMeasureSpec()的源码分析:
getChildMeasureSpec()
//影响:
/根据父视图的MeasureSpec布局参数LayoutParams计算单个子View的MeasureSpec
//即子视图的具体大小由两个方面决定:父视图的MeasureSpec和子视图的LayoutParams属性。
公共静态int getChildMeasureSpec(int 规范, int 填充, int childDimension) {
//参数说明
* @param spec 父视图的详细测量值(MeasureSpec)
* @param padding 视图当前大小的内边距和外边距(padding、margin)
* @param childDimension 子视图的布局参数(宽度/高度)
//父视图的测量模式
int specMode=MeasureSpec.getMode(spec);
//父视图的大小
int specSize=MeasureSpec.getSize(spec);
//通过父视图计算出来的子视图=父尺寸-边距(父视图需要的尺寸,但子视图不一定使用这个值)
int size=Math.max(0, specSize - 填充);
//子view实际需要的尺寸和模式(需要计算)
int 结果大小=0;
int 结果模式=0;
//通过父视图的MeasureSpec和子视图的LayoutParams确定子视图的大小
//当父视图的模式为EXACITY时,父视图将精确值强加于子视图
//一般父视图设置为match_parent或者固定值ViewGroup
开关(规格模式){
案例MeasureSpec.EXACTLY:
//当子视图的LayoutParams0有精确值时
if (子维度=0) {
//子视图的大小是分配给子视图本身的值,模式大小是EXACTLY
结果大小=子维度;
resultMode=MeasureSpec.EXACTLY;
//当子视图的LayoutParams为MATCH_PARENT (-1)时
} else if (childDimension==LayoutParams.MATCH_PARENT) {
//子视图的大小就是父视图的大小,模式为EXACTLY
结果大小=大小;
resultMode=MeasureSpec.EXACTLY;
//当子视图的LayoutParams为WRAP_CONTENT (-2)时
} else if (childDimension==LayoutParams.WRAP_CONTENT) {
//子视图决定自己的大小,但最大不能超过父视图。模式为AT_MOST。
结果大小=大小;
resultMode=MeasureSpec.AT_MOST;
}
休息;
//当父视图的模式为AT_MOST时,父视图对子视图强加一个最大值。 (一般父视图设置为wrap_content)
案例MeasureSpec.AT_MOST:
//原理同上
if (子维度=0) {
结果大小=子维度;
resultMode=MeasureSpec.EXACTLY;
} else if (childDimension==LayoutParams.MATCH_PARENT) {
结果大小=大小;
resultMode=MeasureSpec.AT_MOST;
} else if (childDimension==LayoutParams.WRAP_CONTENT) {
结果大小=大小;
resultMode=MeasureSpec.AT_MOST;
}
休息;
//当父视图的模式为UNSPECIFIED时,父容器对视图没有任何限制。它可以想多大就多大。
//常见于ListView和GridView
案例MeasureSpec.UNSPECIFIED:
if (子维度=0) {
//子视图的大小是分配给子视图本身的值
结果大小=子维度;
resultMode=MeasureSpec.EXACTLY;
} else if (childDimension==LayoutParams.MATCH_PARENT) {
//因为父视图是UNSPECIFIED,所以如果使用MATCH_PARENT,子类的大小为0。
结果大小=0;
resultMode=MeasureSpec.UNSPECIFIED;
} else if (childDimension==LayoutParams.WRAP_CONTENT) {
//因为父视图是UNSPECIFIED,所以WRAP_CONTENT中子类的大小为0
结果大小=0;
resultMode=MeasureSpec.UNSPECIFIED;
}
休息;
}
返回MeasureSpec.makeMeasureSpec(resultSize, resultMode); getChildMeasureSpec()中确定子View的测量模式和尺寸的逻辑有点复杂;别着急,我已经给你总结好了。子View的具体测量方式和尺寸请见下表: Paste_Image.png 从上可以看出,当子View的布局参数使用match_parent或wrap_content时:
子View的SpecMode模式:AT_MOST 子View的SpecSize(宽/高):parenSize=父容器当前剩余空间大小=match_content
4. 问题总结
在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作用相同:等于父容器当前剩余空间
5. 解决方案
当自定义View的布局参数设置为wrap_content时,指定默认大小(宽度/高度)。
具体来说,它是在重写onMeasure() 中设置的
@覆盖
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等类似,在onMeasure()中对wrap_content设置默认宽/高值有特殊处理。读者可以自行查阅。
int mWidth=400;
int mHeight=400;
//当布局参数设置为wrap_content时,设置默认值
if (getLayoutParams().width==ViewGroup.LayoutParams.WRAP_CONTENT getLayoutParams().height==ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
//当width/height任意布局参数=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属性时就会生效。
特别注意
网上流传着这样的解决方案:
@覆盖
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等类似,在onMeasure()中对wrap_content设置默认宽/高值有特殊处理。读者可以自行查阅。
int mWidth=400;
int mHeight=400;
//设置模式为AT_MOST时的默认值(即wrap_content)
if (widthMode==MeasureSpec.AT_MOST heightMode==MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
//当宽度/高度模式为AT_MOST(即wrap_content)时,设置默认值
} else if (widthMode==MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSize);
} else if (heightMode==MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, mHeight);
}上面的解决方案是通过判断测量模式是否为ATMOST来判断View的参数是否为wrap_content。不过,通过下表我们发现,View的AT_MOST模式不仅对应于wrap_content,还对应于match_parent,即当父View为AT_MOST时,将View属性设置为match_parent时
Paste_Image.png 如果我们还是按照上面的做法,当父View为AT_MOST且View为match_parent时,View的match_parent的效果不就等于wrap_content了吗?答:是的,当父View为AT_MOST且View为match_parent时,View的match_parent的效果等于wrap_content。上述方法存在逻辑错误,但由于这种情况很特殊,所以最终的结果是没有错误的。具体分析请看下面的例子:
?xml version="1.0"encoding="utf-8"?--父View设置为wrap_content,即AT_MOST模式--android:layout_width="match_parent"
android:layout_height="match_parent" /Rendering 从上面的效果可以看出,View size=默认值
然后我将子视图的属性更改为wrap_content:
?xml version="1.0"encoding="utf-8"?--父View设置为wrap_content,即AT_MOST模式--android:layout_width="wrap_content"
android:layout_height="wrap_content" /Rendering 从上面的效果可以看出,View的大小仍然等于默认值。
与上面的分析相同
对于第一种情况:当父View为AT_MOST且View为match_parent时,该View的match_parent的效果等于wrap_content。如上所述,这种情况很特殊:父View的大小能刚好包裹子View,子View的大小充满父View的大小。换句话说:父View的大小是看子View的,子View的大小又是看父View的。那么谁看谁的尺寸呢?
答:如果不设置默认值,则继续填充上层VIew的大小,即父View的大小等于顶层View的大小(),则大小子视图=父视图的大小。如果设置了默认值,则使用默认值。我相信你已经明白这一点:
其实上面提到的解决方案(通过判断测量模式是否为AT_MOST来判断View参数是否为wrap_content)只是说明了一些逻辑错误,但就最终结果而言,并没有错,因为当父View为AT_MOST、View为match_parent时,该View的match_parent的效果就等于wrap_content如果默认的话value没有设置,继续填充上层VIew的大小,即父View的大小等于顶层View的大小(),则子View的大小=子View的大小父视图。如果设置了默认值,则使用默认值。为了更好的表示判断逻辑,我建议你们用本文提供的解决方案,即根据布局参数判断默认值的设置
Android 视图自定义:探究设置wrap_content背后的原理和的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!
【Android 视图自定义:探究设置wrap_content背后的原理】相关文章:
用户评论
这篇文章终于来了!我一直想知道怎么让自定义View 完美适配不同分辨率
有15位网友表示赞同!
我也是特别想学习Android自定义View,这个"为什么你设定的wrap..." 我感觉很有意思!
有5位网友表示赞同!
我之前遇到过这个问题,设置了wrap_content但是view不是完全适应内容啊……
有12位网友表示赞同!
期待作者分享一些应对这种情况的技巧!
有9位网友表示赞同!
学习自定义View确实很有学问啊,希望能了解更多相关知识。
有9位网友表示赞同!
这个标题抓住问题关键点了,直接切入主题!
有19位网友表示赞同!
对Android开发有那么点基础的我,想了解一下 wrap_content 的具体用法!
有16位网友表示赞同!
感觉这篇文章能帮助我解决一些自定义View的开发难题!
有15位网友表示赞同!
安卓开发真是太有趣了,自定义View 可以实现很多个性化定制!
有6位网友表示赞同!
分享学习Android自定义View的经验是很有价值的事情哦!
有10位网友表示赞同!
我也曾困扰过这个问题,希望这篇文章能给我一些启发!
有14位网友表示赞同!
如果作者能提供一些代码实例,那更棒了!
有9位网友表示赞同!
学习Android开发一直是我的目标,这篇文章非常适合我!
有15位网友表示赞同!
我希望能从这篇文章中学到很多关于自定义View的知识!
有13位网友表示赞同!
期待一下具体的解决方案,让我不再困惑!
有18位网友表示赞同!
终于有人讲解这个问题了!我也想知道为什么会出现这种情况。
有11位网友表示赞同!
Android开发需要不断学习新的知识和技巧!
有12位网友表示赞同!
这篇文章能帮助我更深入地理解Android自定义View!
有20位网友表示赞同!