您的位置:首页 > 移动开发 > Android开发

android-自定义View解决wrap_content无效的问题

2017-08-19 16:53 555 查看
###问题提出

在我们自定义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。设置值没有特定的标准,以实际情况为准。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: