View的measure过程
View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,子类不能重写此方法。在View的measure方法中会去调用View的onMeasure方法。
View#onMeasure:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
View#getDefaultSize:
public static int getDefaultSize(int size, int measureSpec) {
int result = size; //size大小由getSuggestedMinimumWidth方法或getSuggestedMinimumHeight方法确定
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的SpecMode为AT_MOST跟EXACTLY时,getDefaultSize方法返回的大小就是measureSpec中的specSize,而这个specSize就是View测量后的大小。
- 当前View的SpecMode为UNSPECIFIED时,getDefaultSize方法返回的大小就是getSuggestedMinimumWidth方法或getSuggestedMinimumHeight方法的返回值。
分析getSuggestedMinimumWidth方法:
View#getSuggestedMinimumWidth:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
Drawable#getMinimumWidth:
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
(1)如果View没有设置背景,那么View的宽度为mMinWidth。
mMinWidth对应于android:minWidth这个属性所指定的值,如果不指定则默认为0。
(2)如果View设置了背景,那么View的宽度为max(mMinWidth, mBackground.getMinimumWidth())。
Drawable#getMinimumWidth方法返回的是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0。如ShapeDrawable无原始宽/高,而BitmapDrawable有原始宽/高(图片的尺寸)。
从getDefaultSize方法的实现来看,View的宽/高由SpecSize决定,所有我们可以得出如下结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。
代码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, mHeight);
} elss if(widthSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, heightSpecSize );
} elss if(heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(widthSpecSize , mHeight);
}
}
mWidth、mHeight是给View指定的默认的内部宽/高,并在wrap_content时设置此宽/高。对于非wrap_content情形,沿用系统的测量值。
这个默认的内部宽/高可以根据需要灵活指定,例如TextView、ImageView等控件针对wrap_content情形在onMeasure方法均做了特殊处理。