android-自定义View解决wrap_content无效的问题
2016-02-18 17:33
477 查看
###问题提出
在我们自定义view时,如何需要是当前的view内容自适应,这种平常的使用中,只需要在xml文件中制定宽高或者长高为wrap_content即可,但是如果该view是我们自定义的,那么此时再在xml文件中指定宽高为wrap_content则不能起到内容自适应的效果,并且效果为match_parent。本文即是解决此类问题。
###预备知识
在讲解该问题之前,我们需要了解一些预备知识,以更加清楚的了解原理。
自定义view过程中,我们通常需要关注3个过程,即 测量 / 布局 / 绘制 。 本文的问题位于测量过程,所以本文也将着重了解测量过程。
在测量过程中,其目的是为了得到view的尺寸表示,该表示通过一个封装类为MeasureSpec所标识。测量过程实际上可以说是得到该view的MeasureSpec的过程。
MeasureSpec是通过将一个int(32)的数组成而成的,本质是一个int数,MeasureSpec由两部分组成:SepcMode 和 SpecSize 。其中SpecMode为MeasueSpec的高2位,SpecSize为MeasureSpec的低30位。SpecMode有3类:
UNSPECIFIED:父容器不对View有任何限制,要多大有多大,这种情况一般用于系统内容。在我们使用过程中,一般不考虑这种模式。
EXACTLY:父容器已经检测到了View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的大小。它对应的LayotParams中的match_parent 和具体的数值。
AT_MOST: 父容器指定了一个可用大小SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content。
View的MeasureSpec与父容器的MeasureSpec和本身的LayoutParams有关,并且由二者所决定,决定规则如下:
![](https://static.oschina.net/uploads/img/201602/17191840_emvA.jpg)
###问题出现原理
现在我们看一下View的onMeasure方法。该方法实际上就是View测量过程中最重要的方法。
对于一个View而言,它的onMeasure方法一般由其父容器所调用,并且传入
下面我们具体看一下onMeasure方法的具体实现:
getDefaultSize:
从上面两个方法我们可以知道:在默认实现中,AT_MOST和EXACTLY两种模式都会被设置成specSize。我们也知道AT_MOST对应于wrap_content,而EXACTLY对应于match_parent和具体数值情况。也就说默认情况下wrap_content和match_parent是具有相同的效果的。那有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?
下面讲一下View的绘制过程:
View的绘制首先起于ViewRootImpl,并且View的三个流程也是通过ViewRootImpl来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建viewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。之后,View的绘制过程从ViewRootImpl的performTraversals方法开始,它经过mwasure、layout、draw三个过程最终将一个View绘制出来,由于DecorView是Android的一个页面的顶级View,所以绘制过程首先会从DecorView开始,又因为DecorView是一个ViewGroup,它会遍历绘制所有的子View,我们注意到在protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中会有两个参数,其实这两个参数是在ViewGroup中传入进去的,最初是在DecorView中赋值的,且其值就是屏幕的宽度和高度。
###问题解决
从上面的分析我们知道:出现这个问题的原因是在测量过程中没有处理wrap_content的情况,所以我们在自定义View的时候,如果在直接继承自View,因为View的默认实现是没有处理这种情况的,所以wrap_content会出现与atch_parent相同的效果,要想解决这个问题,我们就要特殊处理。
下面是一种典型的处理方式:
解决的方法总结:
1。指定一个默认的内部宽高,例如本方法中的desiredWidth = 100。
2。判断当MeasureSpec的模式为AT_MOST(对应于wrap_content)时,设置结果为设置的值(最大为我们设置的值,如果小于设置值就设置为specSize)。
3。判断当MeasureSpec的模式为非AT_MOST时,直接设置为系统的测量值即可。
4。设置值没有特定的标准,以实际情况为准。
在我们自定义view时,如何需要是当前的view内容自适应,这种平常的使用中,只需要在xml文件中制定宽高或者长高为wrap_content即可,但是如果该view是我们自定义的,那么此时再在xml文件中指定宽高为wrap_content则不能起到内容自适应的效果,并且效果为match_parent。本文即是解决此类问题。
###预备知识
在讲解该问题之前,我们需要了解一些预备知识,以更加清楚的了解原理。
自定义view过程中,我们通常需要关注3个过程,即 测量 / 布局 / 绘制 。 本文的问题位于测量过程,所以本文也将着重了解测量过程。
在测量过程中,其目的是为了得到view的尺寸表示,该表示通过一个封装类为MeasureSpec所标识。测量过程实际上可以说是得到该view的MeasureSpec的过程。
MeasureSpec是通过将一个int(32)的数组成而成的,本质是一个int数,MeasureSpec由两部分组成:SepcMode 和 SpecSize 。其中SpecMode为MeasueSpec的高2位,SpecSize为MeasureSpec的低30位。SpecMode有3类:
UNSPECIFIED:父容器不对View有任何限制,要多大有多大,这种情况一般用于系统内容。在我们使用过程中,一般不考虑这种模式。
EXACTLY:父容器已经检测到了View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的大小。它对应的LayotParams中的match_parent 和具体的数值。
AT_MOST: 父容器指定了一个可用大小SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content。
View的MeasureSpec与父容器的MeasureSpec和本身的LayoutParams有关,并且由二者所决定,决定规则如下:
![](https://static.oschina.net/uploads/img/201602/17191840_emvA.jpg)
###问题出现原理
现在我们看一下View的onMeasure方法。该方法实际上就是View测量过程中最重要的方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
对于一个View而言,它的onMeasure方法一般由其父容器所调用,并且传入
widthMeasureSpec和
heighMeasureSpec,这两个值就是来源于我们在xml文件中指定的,值为wrap_content / match_parent / 或者具体数值。
下面我们具体看一下onMeasure方法的具体实现:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
getDefaultSize:
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; }
从上面两个方法我们可以知道:在默认实现中,AT_MOST和EXACTLY两种模式都会被设置成specSize。我们也知道AT_MOST对应于wrap_content,而EXACTLY对应于match_parent和具体数值情况。也就说默认情况下wrap_content和match_parent是具有相同的效果的。那有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?
下面讲一下View的绘制过程:
View的绘制首先起于ViewRootImpl,并且View的三个流程也是通过ViewRootImpl来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建viewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。之后,View的绘制过程从ViewRootImpl的performTraversals方法开始,它经过mwasure、layout、draw三个过程最终将一个View绘制出来,由于DecorView是Android的一个页面的顶级View,所以绘制过程首先会从DecorView开始,又因为DecorView是一个ViewGroup,它会遍历绘制所有的子View,我们注意到在protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中会有两个参数,其实这两个参数是在ViewGroup中传入进去的,最初是在DecorView中赋值的,且其值就是屏幕的宽度和高度。
###问题解决
从上面的分析我们知道:出现这个问题的原因是在测量过程中没有处理wrap_content的情况,所以我们在自定义View的时候,如果在直接继承自View,因为View的默认实现是没有处理这种情况的,所以wrap_content会出现与atch_parent相同的效果,要想解决这个问题,我们就要特殊处理。
下面是一种典型的处理方式:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int desiredWidth = 100; int desiredHeight = 100; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; //Measure Width if (widthMode == MeasureSpec.EXACTLY) { //Must be this size width = widthSize; } else if (widthMode == MeasureSpec.AT_MOST) { //Can't be bigger than... width = Math.min(desiredWidth, widthSize); } else { //Be whatever you want width = desiredWidth; } //Measure Height if (heightMode == MeasureSpec.EXACTLY) { //Must be this size height = heightSize; } else if (heightMode == MeasureSpec.AT_MOST) { //Can't be bigger than... height = Math.min(desiredHeight, heightSize); } else { //Be whatever you want height = desiredHeight; } //MUST CALL THIS setMeasuredDimension(width, height); }
解决的方法总结:
1。指定一个默认的内部宽高,例如本方法中的desiredWidth = 100。
2。判断当MeasureSpec的模式为AT_MOST(对应于wrap_content)时,设置结果为设置的值(最大为我们设置的值,如果小于设置值就设置为specSize)。
3。判断当MeasureSpec的模式为非AT_MOST时,直接设置为系统的测量值即可。
4。设置值没有特定的标准,以实际情况为准。
相关文章推荐
- Caused by: android.view.InflateException: Binary XML file line #12: Error inflating class android.support.design.widget.TabLayout,TableLayout引起页面崩溃
- Android Studio Could not find method runProguard() for arguments?
- Android应用开发中触摸屏手势识别的实现方法解析
- Android 开发环境搭建以及编译
- Android monkey 测试命令
- android selector状态详解
- 像QQ空间的Android沉浸式通知栏
- android中连接到指定wifi
- android 对话框
- Android获取FPS的方式
- Rebound-Android的弹簧动画库
- 将Eclipse代码导入到AndroidStudio的两种方式
- 解决Android 中Edittext在执行setError时有时不显示文字
- android颜色对应的xml配置值,颜色表
- 使用RecyclerView出错: Error inflating class android.support.v7.widget.RecyclerView
- 解开Android应用程序组件Activity的"singleTask"之谜
- Android基础之加载动画
- IntentService
- Android XMl文件中tools前缀
- Android蓝牙开发