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方法均做了特殊處理。