View工作流程
View工作流程
1.自定义View流程
开始 -> 构造函数 -> onMeasure() ->onSIzeChanged() ->onLaout() ->onDraw() -> 视图状态改变 -> 完成
- 构造函数:View初始化
- onMeasure:测量View大小
- onSizeChanged:确定View大小
- onLayout:确定子View布局
- onDraw:实际绘制内容
2.MeasureSpec
MeasureSpec代表一个32位Int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode 是指测量模式,二SpecSize是指在某种测量模式下的规格大小。
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作。其提供了打包和解包方法。SpecMode和SpecSize 也是一个int值,一组SpecMode和SpecSize 可以打包成一个MeasureSpec, 而一个MeasureSpec 可以通过解包获得 SpecMode和SpecSize 。这里提到的MeasureSpec是指所代表的int值,而非MeasureSpec本身。
SpecMode有三类:
-
UNSPECIFIED
父容器不对VIew有任何限制,要多大有多大,这种情况一般用于系统内部,表示一种测量的状态。
-
EXACTLY
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。他对应于LayoutParams中的match_parent 和具体数值这两种模式
-
AT_MOST
父容器指定了一个可用大小即SpecSize,VIew的大小不能大于这个值,具体是什么值,要看不同View的具体实现。它对应于LayoutParams 中的 wrap_content。
3.MeasureSpec 和 LayoutParams 的对印关系
系统内部是通过MeasureSpec来进项View的测量,但是正常情况下,我们使用View指定MeasureSpec,尽管如此,但是我们可以给View 设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽高。另外,对于顶级View(DecorView)和普通VIew来说,MeasureSpec 的转换过程略有不同。对于DecorView,其MeasureSpec 由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec 由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasureSpec中就可以确定VIew的测量宽高。
4.View的Measure过程
在View的measure方法中回去调用View的onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension方法设置View的宽/高的测量值,因此我们只需要看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;
}
getDefaultSize 返回的result 就是 MeasureSpec 中的 SpecSize,而这个SpecSize就是View测量后的大小,这里多次提到的测量后的大小,是因为View的最终大小实在layout阶段确定的,所以必须要加以区分,,但是几乎所有情况下View的测量大小和最终大小是相等的。
至于UNSPECIFIED这种情况,一般用于系统内部的测量过程,在这种情况下,View的大小为getDefaultSize的第一个参数size,即宽高分别为 getSuggestedMinimumWidth 、getSuggestedMinimumHeight 这两个方法的返回值。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
从上面可以看出,如果View没有设置背景,那么view的宽度为mMinWidth ,而 mMinWidth 对应于 android:minWidth 这个属性所对应的值,所以 没有设置背景,View的宽度即为 android:minWidth 所指定的值。如果这个属性如果不指定,那么MMinWidth 则 默认为0;如果View指定了背景,则View的宽度为 max(mMinWidth, mBackground.getMinimumWidth());
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
/**
* Returns the drawable's intrinsic width. 返回可绘制对象的固有宽度
* <p>
* Intrinsic width is the width at which the drawable would like to be laid
* out, including any inherent padding. If the drawable has no intrinsic
* width, such as a solid color, this method returns -1.
*
* @return the intrinsic width, or -1 if no intrinsic width
*/
public int getIntrinsicWidth() {
return -1;
}
源码中可以看出,getMinimumWidth返回的就是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0; 那么Drawable 在什么情况下有原始宽度呢? ShapeDrawable 无原始宽高,而BitmapDrawable有原始宽高。(ps: 我还是不懂 o(╥﹏╥)o)
getMinimumWidth:如果View没有设置背景,那么返回android:minWidth所指定的值,可以为0;如果View设置了背景,则返回 android:minWidth 和 背景的最小宽度 这两者中的最大值,最终返回值就是View在UNSPECIFIED情况下 测量的宽高。
EXACTLY | AT_MOST | UNSPECIFIED | |
---|---|---|---|
dp/px | childSize | childSize | childSize |
match_parent | parentSize | parentSize | 0 |
warp_content | parentSize | parentSize | 0 |
从getDefaultSize方法的实现来看,View的宽高有SpecSize决定,所以我们可以得出如下结论:直接继承View的自定义控件需要重写onMeasure方法并设定wrap_content时自身的大小,否则在布局中使用wrap_content就相当于使用match_parent。为什么呢? 从上述代码中我们知道,若果VIew在布局中使用wrap——content,那么它的specMode 是AT_MOST模式,在这种模式下,它的宽高就等于SpecSize;由上表可知,这种情况下View的SpecSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器当前剩余的空间大小。很显然, view的宽高就等于父容器当前剩余空间的大小,这种效果和在布局中使用match_parent完全一致。
那么,如何 解决这个问题呢?
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.