关于自定义View中wrap_content属性失效的问题
2018-01-09 20:32
417 查看
我们在使用自定义控件的时候,有时候会发现当我们设置子View的属性为wrap_content时,发现它最终展现的效果跟我们说预想的不一样,它展现的是match_parent的效果,这是为什么呢?先把问题抛出来,接下来就来简要讲解一下。
问题就出在我们自定义View时的绘制视图阶段,即onMeasure()设置View宽/高这一步。
我们在自定义View的onMeasure方法中,是这样写的:
跟进getDefaultSize
我们可以看到,当View的测量模式为AT_MOST和EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。而AT_MOST对应的是wrap_content,EXACTLY对应的是match_parent。所以不管是设置wrap_content还是match_parent,效果都是match_parent。
划重点!!由于在getDefaultSize( )的默认实现中,当View被设置成wrap_content和match_parent时,View的大小都会被设置成子View MeasureSpec的specSize。所以子View的MeasureSpec的specSize就是关键了。先把看看这个specSize是怎么生成的。
它依赖于父容器的getChildMeasureSpec( )方法:
!!!!!!!
呃……代码真的有点复杂,不过我们可以看看下面这个表格。该表格其实就是代码以表格的形式呈现的效果,是不是很简单明了!?
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,那么针对不同的父容器和View本身不同的LayoutParams,View就可以有多种MeasureSpec。
这里简单说一下:
当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式(EXACTLY)并且大小遵循LayoutParams中的大小。
当View的宽/高是match_parent时,如果父容器的模式是精确模式,那么View也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式(AT_MOST),那么View也是最大模式并且大小不会超过父容器的剩余空间。
当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。可能你会发现,在我们分析中漏掉了UNSPECIFIED模式,那是因为这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。
通过上表就可以看出,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小了。需要说明的是上表不是说明公式或者经验总结,它只是getChildMeasureSpec这个方法以表格的方式呈现出来而已。、
总结一下:
ViewGroup在计算子View MeasureSpec的getChildMeasureSpec( )中,子View MeasureSpec在属性被设置为wrap_content或match_parent情况下,子View MeasureSpec的specSize被设置成parenSize ,即父容器当前剩余空间大小。所以,wrap_content起到了和match_parent相同的作用:等于父容器当前剩余空间大小
怎么解决?这就很简单了,你只要在自定义View的onMeasure( )方法中给出View的默认大小(宽 / 高)就可以了。
网上流传着这么一个解决方案:
这样,有了默认值,View的wrap_content就不会失效啦~
问题就出在我们自定义View时的绘制视图阶段,即onMeasure()设置View宽/高这一步。
我们在自定义View的onMeasure方法中,是这样写的:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //widthMeasureSpec:View宽的测量规格 //heightMeasureSpec:View高的测量规格 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
跟进getDefaultSize
/** * @param size View的默认大小 * @param measureSpec 父容器限制的大小 * @return View的实际测量大小 */ 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; }
我们可以看到,当View的测量模式为AT_MOST和EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。而AT_MOST对应的是wrap_content,EXACTLY对应的是match_parent。所以不管是设置wrap_content还是match_parent,效果都是match_parent。
划重点!!由于在getDefaultSize( )的默认实现中,当View被设置成wrap_content和match_parent时,View的大小都会被设置成子View MeasureSpec的specSize。所以子View的MeasureSpec的specSize就是关键了。先把看看这个specSize是怎么生成的。
它依赖于父容器的getChildMeasureSpec( )方法:
//子view的确切大小由两方面共同决定:父view的MeasureSpec 和 子view的LayoutParams属性 public static int getChildMeasureSpec(int spec, int padding, int 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) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
!!!!!!!
呃……代码真的有点复杂,不过我们可以看看下面这个表格。该表格其实就是代码以表格的形式呈现的效果,是不是很简单明了!?
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,那么针对不同的父容器和View本身不同的LayoutParams,View就可以有多种MeasureSpec。
这里简单说一下:
当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式(EXACTLY)并且大小遵循LayoutParams中的大小。
当View的宽/高是match_parent时,如果父容器的模式是精确模式,那么View也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式(AT_MOST),那么View也是最大模式并且大小不会超过父容器的剩余空间。
当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。可能你会发现,在我们分析中漏掉了UNSPECIFIED模式,那是因为这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。
通过上表就可以看出,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小了。需要说明的是上表不是说明公式或者经验总结,它只是getChildMeasureSpec这个方法以表格的方式呈现出来而已。、
总结一下:
ViewGroup在计算子View MeasureSpec的getChildMeasureSpec( )中,子View MeasureSpec在属性被设置为wrap_content或match_parent情况下,子View MeasureSpec的specSize被设置成parenSize ,即父容器当前剩余空间大小。所以,wrap_content起到了和match_parent相同的作用:等于父容器当前剩余空间大小
怎么解决?这就很简单了,你只要在自定义View的onMeasure( )方法中给出View的默认大小(宽 / 高)就可以了。
网上流传着这么一个解决方案:
@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就不会失效啦~
相关文章推荐
- 自定义View的wrap_content属性失效问题详解
- 关于自定义Dialog 中match_parent 属性 失效的问题
- 自定义View时,wrap_content属性对测量的影响
- android-自定义View解决wrap_content无效的问题
- android-自定义View解决wrap_content无效的问题
- 关于 Kotlin 自定义 View 时,引用系统属性问题
- android-自定义View解决wrap_content无效的问题
- android-自定义View解决wrap_content无效的问题
- Android 自定义View之处理wrap_content,padding问题分析
- 解决ViewPager 高度wrap_content无效问题
- viewpager的layout_width="wrap_content"无效问题
- 关于(UITableViewcell)contentView高度的问题
- 自定义ViewPager实现高度自适应 WRAP_CONTENT|包裹内容
- View在属性为wrap_content/match_parent时获取宽高不准确的解决办法
- onMeasure()源码分析及自定义View对于wrap_content的支持
- 关于自定义dialog中textview的显示的问题
- 自定义组合控件关于LayoutInflater.from(context).inflate(R.layout.view_title, this,true)的问题
- 关于自定义View时,画图形和图片时抗锯齿的使用的问题
- [置顶] RecyclerView常见问题解决方案,RecyclerView嵌套自动滚动,RecyclerView 高度设置wrap_content 无作用等问题
- tableview 自定义cell 不显示 self 与 sel.contentview 的区别 多选右移cell不移动的问题