編輯:關於Android編程
Android中的Veiw從內存中到呈現在UI界面上需要依次經歷三個階段:量算 -> 布局 -> 繪圖,關於View的量算、布局、繪圖的總體機制可參見博文《 Android中View的布局及繪圖機制》。量算是布局和繪圖的基礎,所以量算是很重要的一個環節。本文將從源碼角度解析View的量算過程,這其中會涉及某些關鍵類以及關鍵方法。
對View進行量算的目的是讓View的父控件知道View想要多大的尺寸。
如果要進行量算的View是ViewGroup類型,那麼ViewGroup會在onMeasure方法內會遍歷子View依次進行量算,本文重點說明非ViewGroup的View的量算過程,因為我們一旦了解了非ViewGroup的View的量算過程,ViewGroup的量算理解起來就要簡單許多,主要是ViewGroup在其內部對子View再依次執行量算。
整個應用量算的起點是ViewRootImpl類,從它開始依次對子View進行量算,如果子View是一個ViewGroup,那麼又會遍歷該ViewGroup的子View依次進行量算。也就是說,量算會從View樹的根結點,縱向遞歸進行,從而實現自上而下對View樹進行量算,直至完成對葉子節點View的量算。
那麼到底如何對一個View進行量算呢?Android通過調用View的measure()方法對View進行量算,讓該View的父控件知道該View想要多大的尺寸空間。
具體來說,View的父控件ViewGroup會調用View的measure方法,ViewGroup會將一些寬度和高度的限制條件傳遞給View的measure方法。
在View的measure方法會首先從成員變量中讀取以前緩存過的量算結果,如果能找到該緩存值,那麼就基本完事了,如果沒有找到緩存值,那麼measure方法會執行onMeasure回調方法,measure方法會將上述的寬度和高度的限制條件依次傳遞給onMeasure方法。onMeasure方法會完成具體的量算工作,並將量算的結果通過調用View的setMeasuredDimension方法保存到View的成員變量mMeasuredWidth 和mMeasuredHeight中。
量算完成之後,View的父控件就可以通過調用getMeasuredWidth、getMeasuredState、getMeasuredWidthAndState這三個方法獲取View的量算結果。
以上就是非ViewGroup類型的View量算的總體過程。
上面我們提到ViewGroup在調用View的measure方法時,會傳入ViewGroup對View的寬度及高度的限制條件,這是合理的,例如ViewGroup的空間有限,它需要告訴子View要量算的尺寸的上限。
上面提到的尺寸的限制條件就是MeasureSpec,它可以通過一個Int類型的值來表示的,該Int值會同時包含兩種信息:mode和size,即模式和尺寸。我們知道Java中Int類型的值是4個字節的,Android會用第一個高位字節存儲mode,然後用剩余的三個字節存儲size。
View有一個靜態內部類MeasureSpec,該類有幾個靜態方法以及靜態常量,我們可以用這些方法將mode和size打包成一個Int值或者是從一個Int值中解析出mode和size。
假設我們已有了一個包含MeasureSpec信息的Int值measureSpec,那麼
通過調用MeasureSpec.getSize(int measureSpec)即可從measureSpec解析出三個字節所包含的尺寸size信息,該方法返回Int類型,也就是說我們得到的size實際上就是對原有的measureSpec的高位字節的8個二進制位都設置為0,該方法的返回值size雖然也是4個字節的Int值,但是已經完全不包含mode信息。
通過調用MeasureSpec.getMode(int measureSpec)即可從measureSpec解析出高位字節所包含的模式mode信息,該方法返回Int類型,也就是說我們得到的mode實際上對原有的measureSpec的低位的三個字節的24個二進制碼都設置為0,該方法的返回值mode雖然也是4個字節的Int值,但是已經完全不包含size信息。
對於尺寸size,我們很好理解,比如表示某個寬度值或者表示某個高度值。那麼mode是什麼呢?
mode的取值有三種,分別是:
MeasureSpec.AT_MOST,即0x80000000,該值表示View最大可以取其父ViewGroup給其指定的尺寸,例如現在有個Int值widthMeasureSpec,ViewGroup將其傳遞給了View的measure方法,如果widthMeasureSpec中的mode值是AT_MOST,size是200,那麼表示View能取的最大的寬度是200。
MeasureSpec.EXACTLY,即0x40000000,該值表示View必須使用其父ViewGroup指定的尺寸,還是以widthMeasureSpec為例,如果其mode值是EXACTLY,size是200,那麼表示View的寬度必須是200,不多不少才行。
MeasureSpec.UNSPECIFIED,即0x00000000,該值表示View的父ViewGroup沒有給View在尺寸上設置限制條件,這種情況下View可以忽略measureSpec中的size,View可以取自己想要的值作為量算的尺寸。
更多信息可參考API文檔 android/view/View.MeasureSpec。
measure()的方法簽名是public final void measure(int widthMeasureSpec, int heightMeasureSpec)
。
當View的父控件ViewGroup對View進行量算時,會調用View的measure方法,ViewGroup會傳入widthMeasureSpec和heightMeasureSpec,分別表示父控件對View的寬度和高度的一些限制條件。
measure方法的源碼如下所示:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//首先判斷當前View的layoutMode是不是特例LAYOUT_MODE_OPTICAL_BOUNDS
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
//LAYOUT_MODE_OPTICAL_BOUNDS是特例情況,比較少見
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);
}
//根據widthMeasureSpec和heightMeasureSpec計算key值,我們在下面用key值作為鍵,緩存我們量算的結果
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//mMeasureCache是LongSparseLongArray類型的成員變量,
//其緩存著View在不同widthMeasureSpec、heightMeasureSpec下量算過的結果
//如果mMeasureCache為空,我們就新new一個對象賦值給mMeasureCache
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//mOldWidthMeasureSpec和mOldHeightMeasureSpec分別表示上次對View進行量算時的widthMeasureSpec和heightMeasureSpec
//執行View的measure方法時,View總是先檢查一下是不是真的有必要費很大力氣去做真正的量算工作
//mPrivateFlags是一個Int類型的值,其記錄了View的各種狀態位
//如果(mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT,
//那麼表示當前View需要強制進行layout(比如執行了View的forceLayout方法),所以這種情況下要嘗試進行量算
//如果新傳入的widthMeasureSpec/heightMeasureSpec與上次量算時的mOldWidthMeasureSpec/mOldHeightMeasureSpec不等,
//那麼也就是說該View的父ViewGroup對該View的尺寸的限制情況有變化,這種情況下要嘗試進行量算
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
//通過按位操作,重置View的狀態mPrivateFlags,將其標記為未量算狀態
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//對阿拉伯語、希伯來語等從右到左書寫、布局的語言進行特殊處理
resolveRtlPropertiesIfNeeded();
//在View真正進行量算之前,View還想進一步確認能不能從已有的緩存mMeasureCache中讀取緩存過的量算結果
//如果是強制layout導致的量算,那麼將cacheIndex設置為-1,即不從緩存中讀取量算結果
//如果不是強制layout導致的量算,那麼我們就用上面根據measureSpec計算出來的key值作為緩存索引cacheIndex。
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
//sIgnoreMeasureCache是一個boolean類型的成員變量,其值是在View的構造函數中計算的,而且只計算一次
//一些老的App希望在一次layou過程中,onMeasure方法總是被調用,
//具體來說其值是通過如下計算的: sIgnoreMeasureCache = targetSdkVersion < KITKAT;
//也就是說如果targetSdkVersion的API版本低於KITKAT,即API level小於19,那麼sIgnoreMeasureCache為true
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//如果運行到此處,表示我們沒有從緩存中找到量算過的尺寸或者是sIgnoreMeasureCache為true導致我們要忽略緩存結果
//此處調用onMeasure方法,並把尺寸限制條件widthMeasureSpec和heightMeasureSpec傳入進去
//onMeasure方法中將會進行實際的量算工作,並把量算的結果保存到成員變量中
onMeasure(widthMeasureSpec, heightMeasureSpec);
//onMeasure執行完後,通過位操作,重置View的狀態mPrivateFlags,將其標記為在layout之前不必再進行量算的狀態
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//如果運行到此處,那麼表示當前的條件允許View從緩存成員變量mMeasureCache中讀取量算過的結果
//用上面得到的cacheIndex從緩存mMeasureCache中取出值,不必在調用onMeasure方法進行量算了
long value = mMeasureCache.valueAt(cacheIndex);
//一旦我們從緩存中讀到值,我們就可以調用setMeasuredDimensionRaw方法將當前量算的結果到成員變量中
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//如果我們自定義的View重寫了onMeasure方法,但是沒有調用setMeasuredDimension()方法,
//那麼此處就會拋出異常,提醒開發者在onMeasure方法中調用setMeasuredDimension()方法
//Android是如何知道我們有沒有在onMeasure方法中調用setMeasuredDimension()方法的呢?
//方法很簡單,還是通過解析狀態位mPrivateFlags。
//setMeasuredDimension()方法中會將mPrivateFlags設置為PFLAG_MEASURED_DIMENSION_SET狀態,即已量算狀態,
//此處就檢查mPrivateFlags是否含有PFLAG_MEASURED_DIMENSION_SET狀態即可判斷setMeasuredDimension是否被調用
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException(View with id + getId() + :
+ getClass().getName() + #onMeasure() did not set the
+ measured dimension by calling
+ setMeasuredDimension());
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//mOldWidthMeasureSpec和mOldHeightMeasureSpec保存著最近一次量算時的MeasureSpec,
//在量算完成後將這次新傳入的MeasureSpec賦值給它們
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//最後用上面計算出的key作為鍵,量算結果作為值,將該鍵值對放入成員變量mMeasureCache中,
//這樣就實現了對本次量算結果的緩存,以便在下次measure方法執行的時候,有可能將其從中直接讀出,
//從而省去實際量算的步驟
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL);
}
上面的注釋對每行代碼都進行了詳細的說明,如果大家仔細讀了的話,相信能一目了然,這裡根據上面的注釋簡單總結一下measure方法都干了什麼事:
首先,我們要知道並不是只要View的measure方法執行的時候View就一定要傻傻的真的去做量算工作,View也喜歡偷懶,如果View發現沒有必要去量算的話,那它就不會真的去做量算的工作。
具體來說,View先查看是不是要強制量算以及這次measure中傳入的MeasureSpec與上次量算的MeasureSpec是否相同,如果不是強制量算或者MeasureSpec與上次的量算的MeasureSpec相同,那麼View就不必真的去量算了。
如果不滿足上述條件,View就考慮去做量算工作。但是在量算之前,View還想偷懶,它會以MeasureSpec計算出的key值作為鍵,去成員變量mMeasureCache中查找是否緩存過對應key的量算結果,如果能找到,那麼就簡單調用一下setMeasuredDimensionRaw方法,將從緩存中讀到的量算結果保存到成員變量mMeasuredWidth和mMeasuredHeight中。
如果不能從mMeasureCache中讀到緩存過的量算結果,那麼這次View就真的不能再偷懶了,只能乖乖地調用onMeasure方法去完成實際的量算工作,並且將尺寸限制條件widthMeasureSpec和heightMeasureSpec傳遞給onMeasure方法。關於onMeasure方法,我們會在下面詳細介紹。
不論上面代碼走了哪個判斷的分支,最終View都會得到量算的結果,並且將結果緩存到成員變量mMeasureCache中,以便下次執行measure方法時能夠從其中讀取緩存值。
需要說明的是,View有一個成員變量mPrivateFlags,用以保存View的各種狀態位,在量算開始前,會將其設置為未量算狀態,在量算完成後會將其設置為已量算狀態。
我們在上面提到,當View在measure方法中發現不得不進行實際的量算工作時,將會調用onMeasure方法,並且將尺寸限制條件widthMeasureSpec和heightMeasureSpec作為參數傳遞給onMeasure方法。View的onMeasure方法不是空方法,它提供了一個默認的具體實現。
onMeasure方法的代碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//onMeasure調用了setMeasuredDimension方法,
//setMeasuredDimension又需要調用getDefaultSize方法,
//getDefaultSize又需要調用getSuggestedMinimumWidth和getSuggestedMinimumHeight方法
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我們發現onMeasure方法中會調用setMeasuredDimension方法,setMeasuredDimension又需要調用getDefaultSize方法,getDefaultSize又需要調用getSuggestedMinimumWidth和getSuggestedMinimumHeight方法,即
setMeasuredDimension -> getDefaultSize -> getSuggestedMinimumWidth/Height
那我們就先研究getSuggestedMinimumWidth/Height,然後再依次研究getDefaultSize和setMeasuredDimension,這樣就能把onMeasure方法搞明白了。其實getSuggestedMinimumWidth和getSuggestedMinimumHeight的實現邏輯基本一樣,我們此處只研究getSuggestedMinimumWidth方法即可。
getSuggestedMinimumWidth用於返回View推薦的最小寬度,其代碼如下所示:
protected int getSuggestedMinimumWidth() {
//如果沒有給View設置背景,那麼就返回View本身的最小寬度mMinWidth
//如果給View設置了背景,那麼就取View本身最小寬度mMinWidth和背景的最小寬度的最大值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果沒有給View設置背景,那麼就返回View本身的最小寬度mMinWidth
如果給View設置了背景,那麼就取View本身最小寬度mMinWidth和背景的最小寬度的最大值
那你可能有疑問,View中保存的最小寬度mMinWidth的值是從哪來的呢?實際上有兩種辦法給View設置最小寬度。
第一種情況是,mMinWidth是在View的構造函數中被賦值的,View通過讀取XML中定義的minWidth的值來設置View的最小寬度mMinWidth,以下代碼片段是View構造函數中解析minWidth的部分:
//遍歷到XML中定義的minWith屬性
case R.styleable.View_minWidth:
//讀取XML中定義的屬性值作為mMinWidth,如果XML中未定義,則設置為0
mMinWidth = a.getDimensionPixelSize(attr, 0);
break;
第二種情況是調用View的setMinimumWidth方法給View的最小寬度mMinWidth賦值,setMinimumWidth方法的代碼如下所示:
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
requestLayout();
}
這樣我們就搞明白了getSuggestedMinimumWidth方法是怎麼執行的了,getSuggestedMinimumHeight方法與其邏輯完全一致,只不過是把寬度換成了高度,在此就不再贅述了。
我們在onMeasure方法中發現,onMeasure會執行以下兩行代碼:getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
我們已經研究了getSuggestedMinimumWidth/Height,知道其會返回View的最小寬度和高度,現在我們開始研究getDefaultSize方法。
Android會將View想要的尺寸以及其父控件對其尺寸限制信息measureSpec傳遞給getDefaultSize方法,該方法要根據這些綜合信息計算最終的量算的尺寸。
其源碼如下所示:
public static int getDefaultSize(int size, int measureSpec) {
//size表示的是View想要的尺寸信息,比如最小寬度或最小高度
int result = size;
//從measureSpec中解析出specMode信息
int specMode = MeasureSpec.getMode(measureSpec);
//從measureSpec中解析出specSize信息,不要將specSize與上面的size變量搞混
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//如果mode是UNSPECIFIED,表示View的父ViewGroup沒有給View在尺寸上設置限制條件
case MeasureSpec.UNSPECIFIED:
//此處當mode是UNSPECIFIED時,View就直接用自己想要的尺寸size作為量算的結果
result = size;
break;
//如果mode是UNSPECIFIED,那麼表示View最大可以取其父ViewGroup給其指定的尺寸
//如果mode是EXACTLY,那麼表示View必須使用其父ViewGroup指定的尺寸
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
//此處mode是UNSPECIFIED或EXACTLY時,View就用其父ViewGroup指定的尺寸作為量算的結果
result = specSize;
break;
}
return result;
}
通過以上代碼,我們就會發現View的父ViewGroup傳遞給View的限制條件measureSpec的作用在該方法中體現的淋漓盡致。
首先根據measuredSpec解析出對應的specMode和specSize
當mode是UNSPECIFIED時,View就直接用自己想要的尺寸size作為量算的結果
當mode是UNSPECIFIED或EXACTLY時,View就用其父ViewGroup指定的尺寸作為量算的結果
最終,View會根據measuredSpec限制條件,得到最終的量算的尺寸。
這樣在onMeasure方法中,
當執行getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)時,我們就得到了最終量算到的寬度值;
當執行getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)時,我們就得到了最終量算到的高度值。
在前面我們研究onMeasure方法時就已經看到setMeasuredDimension會調用getDefaultSize方法,會將已經量算到的寬度值和高度值作為參數傳遞給setMeasuredDimension方法,我們研究一下該方法。
其源碼如下所示:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
//layoutMode是LAYOUT_MODE_OPTICAL_BOUNDS的特殊情況,我們不考慮
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
//最終調用setMeasuredDimensionRaw方法,將量算結果傳入進去
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
該方法會在開始判斷layoutMode是不是LAYOUT_MODE_OPTICAL_BOUNDS的特殊情況,這種特例很少見,我們直接忽略掉。
setMeasuredDimension方法最後將量算的結果傳遞給方法setMeasuredDimensionRaw,我們再研究一下setMeasuredDimensionRaw這方法。
setMeasuredDimensionRaw接收兩個參數,分別是已經量算完成的寬度和高度。
其源碼如下所示:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//將量算完成的寬度measuredWidth保存到View的成員變量mMeasuredWidth中
mMeasuredWidth = measuredWidth;
//將量算完成的高度measuredHeight保存到View的成員變量mMeasuredHeight中
mMeasuredHeight = measuredHeight;
//最後將View的狀態位mPrivateFlags設置為已量算狀態
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
我們發現,在該方法中做了三件事:
將量算完成的寬度measuredWidth保存到View的成員變量mMeasuredWidth中
將量算完成的高度measuredHeight保存到View的成員變量mMeasuredHeight中
最後將View的狀態位mPrivateFlags設置為已量算狀態
至此,View的量算過程就完成了,但是View的父ViewGroup如何讀取到View量算的結果呢?
為此,View提供了三組方法,分別是:
1. getMeasuredWidth和getMeasuredHeight方法
2. getMeasuredWidthAndState和getMeasuredHeightAndState方法
3. getMeasuredState方法
有些人可能會納悶,只要有了第一組方法不就行了嗎?後面那兩組方法有啥用?
此處我們要再仔細研究一下View中保存量算結果的成員變量mMeasuredWidth和mMeasuredHeight,下面的討論我們都只討論寬度,理解了寬度的處理方式,高度也是完全一樣的。
mMeasuredWidth是一個Int類型的值,其是由4個字節組成的。
我們先假設mMeasuredWidth只存儲了量算完成的寬度信息,而且View的父ViewGroup可以通過相關方法得到該值。但是存在這樣一種情況:View在量算時,父ViewGroup給其傳遞的widthMeasureSpec中的specMode的值是AT_MOST,specSize是100,但是View的最小寬度是200,顯然父ViewGroup指定的specSize不能滿足View的大小,但是由於specMode的值是AT_MOST,View在getDefaultSize方法中不得不妥協,只能含淚將量算的最終寬度設置為100。然後其父ViewGroup通過某些方法獲取到該View的量算寬度為100時,ViewGroup以為子View只需要100就夠了,最終給了子View寬度為100的空間,這就導致了在UI界面上View特別窄,用戶體驗也就不好。
Android為讓其View的父控件獲取更多的信息,就在mMeasuredWidth上下了很大功夫,雖然是一個Int值,但是想讓它存儲更多信息,具體來說就是把mMeasuredWidth分成兩部分:
其高位的第一個字節為第一部分,用於標記量算完的尺寸是不是達到了View想要的寬度,我們稱該信息為量算的state信息。 其低位的三個字節為第二部分,用於存儲實際的量算到的寬度。由此我們可以看出Android真是物盡其用,一個變量能包含兩個信息,這個有點類似於measureSpec的道理,但是二者又有不同:
measureSpec是將限制條件mode從ViewGroup傳遞給其子View。 mMeasuredWidth、mMeasuredHeight是將帶有量算結果的state標志位信息從View傳遞給其父ViewGroup。那麼你可能會問,在本文中我們沒看到對mMeasuredWidth的高位字節進行特殊處理啊?我們下面看一下View中的resolveSizeAndState方法。
resolveSizeAndState方法與getDefaultSize方法類似,其內部實現的邏輯是一樣的,但是又有區別,getDefaultSize僅僅返回最終量算的尺寸信息,但resolveSizeAndState除了返回最終尺寸信息還會有可能返回量算的state標志位信息。
resolveSizeAndState方法的源碼如下所示:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
//當specMode為AT_MOST,並且父控件指定的尺寸specSize小於View自己想要的尺寸時,
//我們就會用掩碼MEASURED_STATE_TOO_SMALL向量算結果加入尺寸太小的標記
//這樣其父ViewGroup就可以通過該標記其給子View的尺寸太小了,
//然後可能分配更大一點的尺寸給子View
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
當specMode為AT_MOST,並且父控件指定的尺寸specSize小於View自己想要的尺寸時,我們就會用掩碼MEASURED_STATE_TOO_SMALL向量算結果加入尺寸太小的標記,這樣其父ViewGroup就可以通過該標記其給子View的尺寸太小了,然後可能分配更大一點的尺寸給子View。
getDefaultSize方法只是onMeasure方法中獲取最終尺寸的默認實現,其返回的信息比resolveSizeAndState要少,那麼什麼時候才會調用resolveSizeAndState方法呢? 主要有兩種情況:
Android中的許多layout類都調用了resolveSizeAndState方法,比如LinearLayout在量算過程中會調用resolveSizeAndState方法而非getDefaultSize方法。 我們自己在實現自定義的View或ViewGroup時,我們可以重寫onMeasure方法,並在該方法內調用resolveSizeAndState方法。現在我們再回過頭來看以下三組方法:
getMeasuredWidth和getMeasuredHeight方法
該組方法只返回量算結果中的的尺寸信息,去掉了高位字節的state信息,以getMeasuredWidth方法為例,其源碼如下:
public final int getMeasuredWidth() {
//MEASURED_SIZE_MASK的值為0x00ffffff,用mMeasuredWidth與掩碼MEASURED_SIZE_MASK進行按位與運算,
//可以將返回值中的高位字節的8個bit位全置為0,從而去掉了高位字節的state信息
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
MEASURED_SIZE_MASK的值為0x00ffffff,用mMeasuredWidth與掩碼MEASURED_SIZE_MASK進行按位與運算,可以將返回值中的高位字節的8個bit位全置為0,從而去掉了高位字節的state信息
getMeasuredWidthAndState和getMeasuredHeightAndState方法
該組方法返回的量算結果中同時包含尺寸和state信息(如果state存在的話),以getMeasuredWidthAndState方法為例,其源碼如下所示:
public final int getMeasuredWidthAndState() {
//該方法直接返回成員變量mMeasuredWidth,因為mMeasuredWidth本身已經包含了尺寸以及可能的state信息
return mMeasuredWidth;
}
該方法直接返回成員變量mMeasuredWidth,因為mMeasuredWidth本身已經包含了尺寸以及可能的state信息
getMeasuredState方法
該方法返回的Int值中同時包含寬度量算的state以及高度量算的state,不包含任何的尺寸信息,其源碼如下所示:
public final int getMeasuredState() {
//將寬度量算的state存儲在Int值的第一個字節中,即高位首字節
//將高度量算的state存儲在Int值的第三個字節中
return (mMeasuredWidth&MEASURED_STATE_MASK)
| ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
我們簡單分析一下以上代碼:
掩碼MEASURED_STATE_MASK的值為常量0xff000000,其高位字節的8個bit位全為1,剩余低位字節的三個字節的24個bit位全為0
MEASURED_HEIGHT_STATE_SHIFT的值為常量16
當執行(mMeasuredWidth&MEASURED_STATE_MASK)時,將mMeasuredWidth與MEASURED_STATE_MASK進行按位與操作,該表達式的值高位字節保留了量算後寬度的state,過濾掉了其低位三個字節所存儲的寬度size
由於我們已經用高位首字節存儲了量算後寬度的state,所以高度的state就不能存儲在高位首字節了。Android打算把它存儲在第三個字節中。(mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)表示將mMeasuredHeight向右移16位,這樣高度的state字節就從原來的第一個字節右移動到了第三個字節,由於高度的state向右移動了,所以其對應的掩碼也有相應移動。(MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT)表示state的掩碼也從第一個字節右移16位到了第三個字節,即掩碼從0xff000000變成了0x0000ff00。然後用右移後的state與右移後的掩碼執行按位與操作,這樣就在第三個字節保留了高度的state信息,並且過濾掉了第1、2、4字節中的信息,即將這三個字節中的24個bit位置為0。
最後,將我們得到的寬度的state與高度的state進行按位或操作,這樣就將寬度和高度的state都保存在一個Int值中:第一個字節存儲寬度的state,第三個字節存儲高度的state。
關鍵詞:hciconfighcitool hcidump作者:xubin341719(歡迎轉載,請注明作者,請尊重版權,謝謝!)歡迎指正錯誤,共同學習、共同進步!!An
本文實例講述了Android編程實現可滑動的開關效果。分享給大家供大家參考,具體如下:閒著沒事,把之前寫的一個Demo放上來分享下。就是一個開關,實現可滑動和動畫效果。不
Android Material Design:PopupMenuAndroid Material Design 引入的PopupMenu類似過去的上下文菜單,但是更靈活
一、操作XML文檔概述1、如何操作XML文檔XML文檔也是數據的一種,對數據的操作也不外乎是“增刪改查”。也被大家稱之為“CRUD&r