View的工作原理之Measure過程原始碼學習(三)
上一篇文章講解了整個Android應用程式的View檢視的頂級節點DecorView的Measure過程,文章最後就講到了DecorView的onMeasure方法中呼叫super.onMeasure(widthMeasureSpec, heightMeasureSpec);之後,在FrameLayout的onMeasure方法中通過迴圈遍歷子元素,從而往下進行每一級View的Measure過程。
在開始本文內容講解之前,我們需要先理解一些概念。在Android原始碼中,我們可以發現,所有的View控制元件都是直接或間接繼承自View這個類,包括ViewGroup也是。所以這裡我們有必要進行一下區分,我們知道ViewGroup是可以有子元素的View,那麼對於不能有子元素的View,我們稱之為普通View
回顧第一篇文章中,最後部分說到,DecorView的Measure過程是在performMeasure方法中呼叫DecorView的measure方法開始,而子元素的Measure過程是在DecorView的父類FrameLayout中的onMeasure方法中呼叫子元素的measure方法開始的。這就可以知道,不管是哪個ViewMeasure過程,一定會呼叫到它的measure方法。檢視原始碼發現,這個measure方法是在View類中定義的,並且它是final型別
//View.java 21961行 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { //光學邊界處理 Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); } //... if (forceLayout || needsLayout) { //... if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } //... } }
在View的measure方法程式碼中,可以看到,前面有部分是光學邊界處理的。至於這光學邊界是什麼來的,我還不是很瞭解,但是這裡還是提一下,防止以後再看程式碼糾結。程式碼省略部分大多是判斷條件的處理,我認為不需要過多關注,所以註釋了。這裡其實重點是onMeasure和setMeasuredDimensionRaw這兩個方法。整個measure方法中重點就是,根據判斷條件的不同,分別呼叫這兩個方法。根據註釋及相關程式碼,我猜測應該是,為了避免重複測量,所以會在測量的時候靜得到的測量值快取下來,然後下一次再需要測量的時候,檢查是不是有快取的測量值。有得話就拿出來呼叫setMeasuredDimensionRaw方法就行,沒有就呼叫onMeasure方法進行測量,值的一提的是onMeasure方法的引數就是父容器的MeasureSpec值,這個是可以在onMeasure方法註釋中瞭解到。下面來看View的這兩個方法。
//View.java 22115行
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
//View.java 22089行
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
//View.java 22072行
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent
* @param heightMeasureSpec Vertical space requirements as imposed by the parent
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到,setMeasuredDimensionRaw方法很簡單,將拿到的測量值,賦給全域性變數就完成了。onMeasure方法最終也是呼叫setMeasuredDimension方法,這裡又是光學邊界處理,然後呼叫setMeasuredDimensionRaw方法,也是把處理得到的值給到全域性變數。
到這,可以很直接的說,所有View的measure過程的最終目的就是把測量得到的值賦值給mMeasuredHeight和mMeasuredWidth。很顯然View的測量結果,就是為了View的佈局(layout)過程和繪畫(draw)過程服務的。在這兩個過程,都有使用到getMeasuredWidth和getMeasuredHeight方法來獲取他們終的SpecSize。程式碼如下,之所以需要“與運算”,是因為mMeasuredWidth和mMeasuredHeight包含測量模式和測量大小,這裡只取大小。
//View.java 13626行
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
//View.java 13654行
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
View的measure過程到這,其實就可以看出一些東西了。接下來的步驟就是具體的實現View重寫onMeasure方法,然後進行相應的處理了。那麼當我們自定義View的時候重寫的onMeasure方法,到底該做些什麼呢?我們在這裡是不得而知的。關於這個問題,我們可以參看目前Android已經寫好的控制元件,看看這些控制元件是如何實現的,就可以知道,當我們自定義View的時候該怎麼做了。
先來看一下,預設情況下View的onMeasure方法是怎麼做的,看原始碼:
//View.java 22072行
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//View.java 22232行
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//Drawable.java 1052行
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
//Drawable.java 1026行
public int getIntrinsicWidth() {
return -1;
}
在onMeasure方法中,setMeasuredDimension方法傳遞的引數是getDefaultSize方法返回來的,getDefaultSize方法的引數又是getSuggestedMinimumWidth和父容器的widthMeasureSpec(這裡只分析width的情況,height的情況類似)。在getSuggestedMinimumWidth方法中,檢視背景是否設定,如果沒有設定背景,那麼View的寬度是mMinWidth,而mMinWidth對應android:minWidth這個屬性的值,如果這個屬性沒有設定,那麼預設0。如果View設定了背景,那麼getSuggestedMinimumWidth方法返回的值是max(mMinWidth, mBackground.getMinimumWidth())。通過程式碼得知,mBackground.getMinimumWidth()返回值為intrinsicWidth,intrinsicWidth的值是Drawable的原始寬度,有些Drawable由原始寬度,有些沒有,如果沒有則直接返回0。
//View.java 22188行
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方法也很簡單, View的寬高就是specSize(不考慮MeasureSpec.UNSPECIFIED模式的情況)。所以在預設的onMeasure方法中,View的寬高就是父容器剩餘的大小,也就是父容器的MeasureSpec值的SpecSize。由此我們得出一個結論:不管父容器的測量模式是什麼,預設情況下,子元素的測量大小,都是父容器剩餘的大小。到這瞭解了預設情況下View的onMeasure方法的工作。下一篇文章,將會學習ViewGroup和普通View的onMeasure方法的工作。