Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 源碼解析Android中View的measure量算過程

源碼解析Android中View的measure量算過程

編輯:關於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量算的總體過程。


MeasureSpec簡介

上面我們提到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方法

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的各種狀態位,在量算開始前,會將其設置為未量算狀態,在量算完成後會將其設置為已量算狀態。


onMeasure方法

我們在上面提到,當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方法

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方法與其邏輯完全一致,只不過是把寬度換成了高度,在此就不再贅述了。


getDefaultSize

我們在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)時,我們就得到了最終量算到的高度值。


setMeasuredDimension

在前面我們研究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

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設置為已量算狀態


量算完成的尺寸的state

至此,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

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方法。

getMeasuredXXX系列方法

現在我們再回過頭來看以下三組方法:

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。


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved