天天看點

Android源碼完全解析——View的Measure過程

在Android中,Veiw從記憶體中到呈現在UI界面上需要經過measure(測量)、layout(布局)、draw(繪制)這樣一個過程。為什麼需要measure過程?因為在Android中View有自适應尺寸的機制,在用自适應尺寸來定義View大小的時候,View的真實尺寸還不能确定,這時候就需要根據View的寬高比對規則,經過計算,得到具體的像素值,measure過程就是幹這件事。

本文将從源碼角度解析View的measure過程,這其中會涉及某些關鍵類以及關鍵方法。

Android源碼完全解析——View的Measure過程
文章幹貨很長 文末有福利!!

MeasureSpec

MeasureSpec封裝了父布局傳遞給子布局的布局要求,它通過一個32位int類型的值來表示,該值包含了兩種資訊,高兩位表示的是

SpecMode

(測量模式),低30位表示的是

SpecSize

(測量的具體大小)。下面通過注釋的方式來分析來類:

/**  
 * 三種SpecMode: 
 * 1.UNSPECIFIED 
 * 父ViewGroup沒有對子View施加任何限制,子view可以是任意大小。這種情況比較少見,主要用于系統内部多次measure的情形,用到的一般都是可以滾動的容器中的子view,比如ListView、GridView、RecyclerView中某些情況下的子view就是這種模式。一般來說,我們不需要關注此模式。
 * 2.EXACTLY 
 * 該view必須使用父ViewGroup給其指定的尺寸。對應match_parent或者具體數值(比如30dp)
 * 3.AT_MOST 
 * 該View最大可以取父ViewGroup給其指定的尺寸。對應wrap_content
 *  
 * MeasureSpec使用了二進制去減少對象的配置設定。 
 */  
public class MeasureSpec {  
        // 進位大小為2的30次方(int的大小為32位,是以進位30位就是要使用int的最高位和第二高位也就是32和31位做标志位)  
        private static final int MODE_SHIFT = 30;  
          
        // 運算遮罩,0x3為16進制,10進制為3,二進制為11。3向左進位30,就是11 00000000000(11後跟30個0)  
        // (遮罩的作用是用1标注需要的值,0标注不要的值。因為1與任何數做與運算都得任何數,0與任何數做與運算都得0)  
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
  
        // 0向左進位30,就是00 00000000000(00後跟30個0)  
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
        // 1向左進位30,就是01 00000000000(01後跟30個0)  
        public static final int EXACTLY     = 1 << MODE_SHIFT;  
        // 2向左進位30,就是10 00000000000(10後跟30個0)  
        public static final int AT_MOST     = 2 << MODE_SHIFT;  
  
        /** 
         * 根據提供的size和mode得到一個詳細的測量結果 
         */  
        // 第一個return:
        // measureSpec = size + mode;   (注意:二進制的加法,不是十進制的加法!)  
        // 這裡設計的目的就是使用一個32位的二進制數,32和31位代表了mode的值,後30位代表size的值  
        // 例如size=100(4),mode=AT_MOST,則measureSpec=100+10000...00=10000..00100  
        // 
        // 第二個return:
        // size & ~MODE_MASK就是取size 的後30位,mode & MODE_MASK就是取mode的前兩位,最後執行或運算,得出來的數字,前面2位包含代表mode,後面30位代表size
        public static int makeMeasureSpec(int size, int mode) {  
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }  
  
        /** 
         * 獲得SpecMode
         */  
        // mode = measureSpec & MODE_MASK;  
        // MODE_MASK = 11 00000000000(11後跟30個0),原理是用MODE_MASK後30位的0替換掉measureSpec後30位中的1,再保留32和31位的mode值。  
        // 例如10 00..00100 & 11 00..00(11後跟30個0) = 10 00..00(AT_MOST),這樣就得到了mode的值  
        public static int getMode(int measureSpec) {  
            return (measureSpec & MODE_MASK);  
        }  
  
        /** 
         * 獲得SpecSize 
         */  
        // size = measureSpec & ~MODE_MASK;  
        // 原理同上,不過這次是将MODE_MASK取反,也就是變成了00 111111(00後跟30個1),将32,31替換成0也就是去掉mode,保留後30位的size  
        public static int getSize(int measureSpec) {  
            return (measureSpec & ~MODE_MASK);  
        }  
}  
           

measure()

當View的父ViewGroup對View進行測量時,會調用View的

measure

方法,ViewGroup會傳入

widthMeasureSpec

heightMeasureSpec

,分别表示父控件對View的寬度和高度的一些限制條件。源碼分析該方法:

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下測量過的結果
        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,這時候有可能找到相應的值,找到就傳回對應索引;也可能找不到,找不到就傳回-1
            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);

            if (cacheIndex < 0 || sIgnoreMeasureCache) {
            //在緩存中找不到相應的值或者需要忽略緩存結果的時候,重新測量一次
            //此處調用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()");
            }
        //到了這裡,View已經測量完了并且将測量的結果儲存在View的mMeasuredWidth和mMeasuredHeight中,将标志位置為可以layout的狀态
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    //mOldWidthMeasureSpec和mOldHeightMeasureSpec儲存着最近一次測量時的MeasureSpec,
    //在測量完成後将這次新傳入的MeasureSpec指派給它們
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    //最後用上面計算出的key作為鍵,測量結果作為值,将該鍵值對放入成員變量mMeasureCache中,
    //這樣就實作了對本次測量結果的緩存,以便在下次measure方法執行的時候,有可能将其從中直接讀出,
    //進而省去實際測量的步驟
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
           

上面的注釋已經一目了然,這裡再總結一下

measure()

都幹了什麼事:

  • 首先,調用

    View.measure()

    方法時View并不是立即就去測量,而是先判斷一下是否有必要進行測量操作,如果不是強制測量或者

    MeasureSpec

    與上次的

    MeasureSpec

    相同的時候,那麼View就不需要重新測量了.
  • 如果不滿足上面條件,View就考慮去做測量工作了.但在測量之前,View還想偷懶,如果能在緩存中找到上次的測量結果,那直接從緩存中擷取就可以了.它會以MeasureSpec計算出的key值作為鍵,去成員變量

    mMeasureCache

    中查找是否緩存過對應key的測量結果,如果能找到,那麼就簡單調用一下

    setMeasuredDimensionRaw

    方法,将從緩存中讀到的測量結果儲存到成員變量

    mMeasuredWidth

    mMeasuredHeight

    中。
  • 如果不能從

    mMeasureCache

    中讀到緩存過的測量結果,那麼這次View就真的不能再偷懶了,隻能乖乖地調用

    onMeasure()

    方法去完成實際的測量工作,并且将尺寸限制條件

    widthMeasureSpec

    heightMeasureSpec

    傳遞給

    onMeasure()

    方法。關于

    onMeasure()

    方法,我們會在下面詳細介紹。
  • 不論上面代碼走了哪個判斷的分支,最終View都會得到測量的結果,并且将結果儲存到

    mMeasuredWidth

    mMeasuredHeight

    這兩個成員變量中,同時緩存到成員變量

    mMeasureCache

    中,以便下次執行

    measure()

    方法時能夠從其中讀取緩存值。
  • 需要說明的是,View有一個成員變量

    mPrivateFlags

    ,用以儲存View的各種狀态位,在測量開始前,會将其設定為未測量狀态,在測量完成後會将其設定為已測量狀态。

onMeasure()

上面我們提到,View的

measure()

方法在需要進行實際的測量工作時會調用

onMeasure()

方法.看下源碼:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
           

我們發現

onMeasure()

方法中調用了

setMeasuredDimension()

方法,

setMeasuredDimension()

又調用了

getDefaultSize()

方法.

getDefaultSize()

又調用了

getSuggestedMinimumWidth()

getSuggestedMinimumHeight()

,那我們反向研究一下,先看下

getSuggestedMinimumWidth()

方法(

getSuggestedMinimumHeight()

原理

getSuggestedMinimumWidth()

跟一樣).

getSuggestedMinimumWidth()

該方法傳回View推薦的最小寬度,源碼如下:

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
           

源碼很簡單,如果View沒有背景,就直接傳回View本身的最小寬度

mMinWidth

;如果給View設定了背景,就取View本身的最小寬度

mMinWidth

和背景的最小寬度的最大值.

那麼

mMinWidth

是哪裡來的?搜尋下源碼就可以知道,View的最小寬度

mMinWidth

可以有兩種方式進行設定:

  • 第一種是在View的構造方法中進行指派的,View通過讀取XML檔案中View設定的

    minWidth

    屬性來為

    mMinWidth

    指派:
    case R.styleable.View_minWidth:
         mMinWidth = a.getDimensionPixelSize(attr, 0);
         break;
               
  • 第二種是在調用View的

    setMinimumWidth

    方法為

    mMinWidth

    指派:
    public void setMinimumWidth(int minWidth) {
        mMinWidth = minWidth;
        requestLayout();
    }
               

getDefaultSize()

知道了

getSuggestedMinimumWidth()/getSuggestedMinimumHeight()

這兩個方法傳回的是View的最小寬度/高度之後,我們将得到的最小寬度/高度值作為參數傳給

getDefaultSize(int size, int measureSpec)

方法,看下源碼:

public static int getDefaultSize(int size, int measureSpec) {
      //size是傳進來的View自己想要的最小寬度/高度
        int result = size;
      //measureSpec是父ViewGroup給View的限制條件,解析出specMode和specSize
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
       //如果specMode為UNSPECIFIED,就表明父ViewGroup沒有對該View尺寸進行限制,直接取View自己想要的寬高
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //如果specMode為EXACTLY,表明父ViewGroup要求該View必須用父ViewGroup指定的尺寸(specSize),取父ViewGroup指定的寬高值
        //如果specMode為AT_MOST,表明ViewGroup給該View指定了最大寬度/高度尺寸(specSize),取父ViewGroup指定的最大寬度/高度。
        //這裡肯定有人有疑問了?為什麼specMode為AT_MOST是取View能到達的最大寬高值specSize,跟EXACTLY模式下的取值一模一樣,聯想到EXACTLY對應match_parent,AT_MOST對應wrap_content,那這樣wrap_content不就跟match_parent一樣的效果麼?是的,調用這個方法在測量的時候,wrap_content确實跟match_parent一樣的效果,這樣做有可能是Android還沒适配wrap_content而做的簡單處理,就像Recyclerview早期的版本就沒有适配wrap_content,導緻wrap_content和match_parent一樣的效果,直到23.2.0版本才将match_parent和wrap_content區分開來。那适配了wrap_content的測量方法在哪裡呢?下文會講到。
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
           

通過代碼可以看到,父ViewGroup通過

measureSpec

對View尺寸的限制作用已經展現出來了。最終通過該方法可以得到View在符合ViewGroup的限制條件下的預設尺寸,即

getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)

:獲得該View在符合ViewGroup的限制條件下的預設寬度值

getDefaultSize(getSuggestedMinimumHeight(), widthMeasureSpec)

:獲得該View在符合ViewGroup的限制條件下的預設高度值

從注釋可以看出,

getDefaultSize()

這個測量方法并沒有适配

wrap_content

這一種布局模式,隻是簡單地将

wrap_content

match_parent

等同起來。

到了這裡,我們要注意一個問題,

getDefaultSize()

方法中

wrap_content

match_parent

屬性的效果是一樣的,而該方法是View的

onMeasure()

中預設調用的,也就是說,對于一個直接繼承自View的自定義View來說,它的wrap_content和match_parent屬性是一樣的效果,是以如果要實作自定義View的

wrap_content

,則要重寫

onMeasure()

方法,對

wrap_content

屬性進行處理。如何處理呢?也很簡單,代碼如下所示:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  //取得父ViewGroup指定的寬高測量模式和尺寸
  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) {
    //如果寬高都是AT_MOST的話,即都是wrap_content布局模式,就用View自己想要的寬高值
        setMeasuredDimension(mWidth, mHeight);
  }else if (widthSpecMode == MeasureSpec.AT_MOST) {
    //如果隻有寬度都是AT_MOST的話,即隻有寬度是wrap_content布局模式,寬度就用View自己想要的寬度值,高度就用父ViewGroup指定的高度值
        setMeasuredDimension(mWidth, heightSpecSize);
  }else if (heightSpecMode == MeasureSpec.AT_MOST) {
    //如果隻有高度都是AT_MOST的話,即隻有高度是wrap_content布局模式,高度就用View自己想要的寬度值,寬度就用父ViewGroup指定的高度值
        setMeasuredDimension(widthSpecSize, mHeight);
  }
}
           

在上面的代碼中,我們要給View指定一個預設的内部寬/高(

mWidth

mHeight

),并在

wrap_content

時設定此寬/高即可。

setMeasuredDimension()

現在再來看下

setMeasuredDimension()

這個方法,該方法将通過

getDefaultSize()

得到的寬高值作為參數傳進去,看下源碼都幹了些什麼:

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);
    }
           

該方法會在開始判斷layoutMode是不是

LAYOUT_MODE_OPTICAL_BOUNDS

的特殊情況,這種特例很少見,我們直接忽略掉。

setMeasuredDimension()

方法最後将寬高值傳遞給方法

setMeasuredDimensionRaw()

,我們再研究一下

setMeasuredDimensionRaw()

這方法。

setMeasuredDimensionRaw()

該方法接受兩個參數,也就是測量完的寬度和高度,看源碼:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
           

看到了吧,這裡就是把測量完的寬高值指派給

mMeasuredWidth

mMeasuredHeight

這兩個View的屬性,然後将标志位置為已測量狀态。

View寬高尺寸值的state

至此,由父ViewGroup發起的向它内部的每個子View發送measure指令,然後各個View根據ViewGroup給的限制條件測量出來的寬高尺寸已經存到View的

mMeasuredWidth

mMeasuredHeight

這兩個屬性當中。但ViewGroup怎麼知道他的子View是多大呢?View提供了以下三組方法:

  1. getMeasuredWidth()

    getMeasuredHeight()

  2. getMeasuredWidthAndState()

    getMeasuredHeightAndState()

  3. getMeasuredState()

通過方法名稱可以猜出寬高的尺寸有state這個概念,我們再來研究View中儲存測量結果的屬性

mMeasuredWidth

mMeasuredHeight

,其實隻要讨論

mMeasuredWidth

就可以了,

mMeasuredHeight

一樣的道理。

mMeasuredWidth

是一個Int類型的值,其是由4個位元組組成的。

Android為讓其View的父控件擷取更多的資訊,就在

mMeasuredWidth

上下了很大功夫,雖然是一個Int值,但是想讓它存儲更多資訊,具體來說就是把

mMeasuredWidth

分成兩部分:

  • 其高位的第一個位元組為第一部分,用于标記測量完的尺寸是不是達到了View想要的寬度,我們稱該資訊為測量的state資訊。
  • 其低位的三個位元組為第二部分,用于存儲測量到的寬度。

一個變量能包含兩個資訊,這個有點類似于

measureSpec

,但是二者又有不同:

  • measureSpec

    是将限制條件從ViewGroup傳遞給其子View。
  • mMeasuredWidth

    mMeasuredHeight

    是将帶有測量結果的state标志位資訊從View傳遞給其父ViewGroup。

那是在哪裡有對

mMeasuredWidth

的第一個位元組進行處理呢?可以看到我們下面看一下View中的

resolveSizeAndState()

方法。

resolveSizeAndState()

這是View一個很重要的測量方法,直接看源碼:

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:
            //當specMode為AT_MOST時,這時候specSize是父ViewGroup給該View指定的最大尺寸
                if (specSize < size) {
                  //如果父ViewGroup指定的最大尺寸比View想要的尺寸還要小,這時候會使用MEASURED_STATE_TOO_SMALL這個掩碼向已經測量出來的尺寸specSize加入尺寸太小的标志,然後将這個帶有标志的specSize傳回
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                  //如果父控件指定最大尺寸沒有比View想要的尺寸小,這時候就放棄之前已經給View指派的specSize,用View自己想要的尺寸就可以了。
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }
           

這個方法的代碼結構跟前文提到的

getDefaultSize()

方法很相似,主要的差別在于

specMode

為AT_MOST的情況。我們當時說

getDefaultSize()

方法是沒有适配

wrap_content

這種情況,而這個

resolveSizeAndState()

方法是已經适配了

wrap_content

的布局方式,那具體怎麼實作AT_MOST測量邏輯的呢?有兩種情況:

  • 當父ViewGroup指定的最大尺寸比View想要的尺寸還要小時,會給這個父ViewGroup的指定的最大值

    specSize

    加入一個尺寸太小的标志MEASURED_STATE_TOO_SMALL,然後将這個帶有标志的尺寸傳回,父ViewGroup通過該标志就可以知道配置設定給View的空間太小了,在視窗協商測量的時候會根據這個标志位來做視窗大小的決策。
  • 當父ViewGroup指定的最大尺寸比沒有比View想要的尺寸小時(相等或者View想要的尺寸更小),直接取View想要的尺寸,然後傳回該尺寸。

getDefaultSize()

方法隻是

onMeasure()

方法中擷取最終尺寸的預設實作,其傳回的資訊比

resolveSizeAndState()

要少,那麼什麼時候才會調用

resolveSizeAndState()

方法呢? 主要有兩種情況:

  • Android中的大部分layout類都調用了

    resolveSizeAndState()

    方法,比如LinearLayout在測量過程中會調用

    resolveSizeAndState()

    方法而非

    getDefaultSize()

    方法。
  • 我們自己在實作自定義的View或ViewGroup時,我們可以重寫

    onMeasure()

    方法,并在該方法内調用

    resolveSizeAndState()

    方法。

getMeasureXXX系列方法

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

  • getMeasuredWidth()

    getMeasuredHeight()

    該組方法隻傳回測量結果的尺寸資訊,去除掉高位位元組的state資訊,以

    getMeasuredWidth()

    為例,源碼如下:
    public final int getMeasuredWidth() {
          // MEASURED_SIZE_MASK = 0x00ffffff,mMeasuredWidth與MEASURED_SIZE_MASK作與運算,
          // 就能将mMeasuredWidth的最高位元組全部置0,進而去掉state資訊
            return mMeasuredWidth & MEASURED_SIZE_MASK;
        }
               
  • getMeasuredWidthAndState()

    getMeasuredHeightAndState()

    該組方法傳回測量結果同時包含尺寸和state資訊,以

    getMeasuredWidthAndState()

    為例,源碼如下:
    public final int getMeasuredWidthAndState() {
          //由于mMeasuredWidth完整包含了尺寸和state資訊,直接傳回該資訊
            return mMeasuredWidth;
        }
               
  • 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位全部為1,低位元組的24位全部為0。
    • MEASURED_HEIGHT_STATE_SHIFT值為16。
    • 将MEASURED_STATE_MASK與

      mMeasuredWidth

      做與操作之後就取出了存儲在寬度首位元組中的state資訊,過濾掉低位三個位元組的尺寸資訊。
    • 由于int有四個位元組,首位元組已經存了寬度的state資訊,那麼高度的state資訊就不能存在首位位元組。MEASURED_STATE_MASK向右移16位,變成了0x0000ff00,這個值與高度值

      mMeasuredHeight

      做與操作就取出了

      mMeasuredHeight

      第三個位元組中的資訊。而

      mMeasuredHeight

      的state資訊是存在首位元組中,是以也得對

      mMeasuredHeight

      向右移相同的位置,這樣就把state資訊移到了第三個位元組中。
    • 最後,将得到的寬度state與高度state按位或操作,這樣就拼接成一個int值,該值首個位元組存儲寬度的state資訊,第三個位元組存儲高度的state資訊。

ViewGroup的measure過程

通過上面的介紹已經知道了單個View的測量過程,現在看下ViewGroup是怎樣測量的。

對于ViewGroup來說,除了完成自己的measure過程,還會周遊去調用所有子元素的

measure()

方法,各個子元素再遞歸去執行這個過程。和View不同的是,ViewGroup是一個抽象類,它提供了一個叫

measureChildren()

的方法用于測量子元素,源碼如下:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            //周遊每個子元素,如果該子元素不是GONE的話,就去測量該子元素
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
           

從上述代碼來看,ViewGroup在measure時,會調用

measureChild()

這個方法對每一個子元素進行測量,該方法源碼如下:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        //擷取child自身的LayoutParams屬性
        final LayoutParams lp = child.getLayoutParams();
        //根據父布局的MeasureSpec,父布局的padding和child的LayoutParams這三個參數,通過getChildMeasureSpec()方法計算出子元素的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        //調用measure()方法測量child,前文已經解釋過這個方法,調用該方法之後會将view的寬高值儲存在mMeasuredWidth和mMeasuredHeight這兩個屬性當中,這樣child的尺寸就已經測量出來了
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
           

很顯然,

measureChild()

的思想就是取出子元素的LayoutParams,然後再通過

getChildMeasureSpec()

方法來建立子元素的MeasureSpec,接着将MeasureSpec傳給View的

measure()

方法來完成對子元素的測量。重點看下

getChildMeasureSpec()

這個方法。

getChildMeasureSpec()

該方法是根據父容器的MeasureSpec、padding和子元素的LayoutParams屬性得到子元素的MeasureSpec,進而根據這個MeasureSpec來測量子元素。源碼如下:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //取得SpecMode和SpecSize
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //子元素的可用大小為父容器的尺寸減去padding
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        //父容器是EXACTLY模式,表明父容器本身的尺寸已經是确定的了
        case MeasureSpec.EXACTLY:
            //childDimension是子元素的屬性值,如果大于等于0,就說明該子元素是指定寬/高尺寸的(比如20dp),
            //因為MATCH_PARENT的值為-1,WRAP_CONTENT的值為-2,都是小于0的,是以大于等于0肯定是指定固定尺寸的。
            //既然子元素都指定固定大小了,就直接取指定的尺寸,
            //然後将子元素的測量模式定為EXACTLY模式,表明子元素的尺寸也确定了
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 如果子元素是MATCH_PARENT,也就是希望占滿父容器的空間,那子元素的尺寸就取父容器的可用空間大小,模式也是EXACTLY,表明子元素的尺寸也确定了
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              // 如果子元素是WRAP_CONTENT,也就是寬/高希望能包裹自身的内容就可以了,
              //但由于這時子元素自身還沒測量,無法知道自己想要多大的尺寸,
              //是以這時就先取父容器給子元素留下的最大空間,模式為AT_MOST,表示子元素的寬/高不能超過該最大值
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父容器的尺寸還沒确定,但是不能超過最大值
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // 子元素指定了大小,就取子元素的尺寸,模式為EXACTLY,表明該子元素确定了尺寸
                // 這時父容器的限制對子元素來說是不起作用的,子元素的尺寸是可以超出了父容器的大小,超出的部分是顯示不出來的
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
               // 子元素是MATCH_PARENT,表明子元素希望占滿父容器,
               //但是父容器自身的大小還沒确定,也無法給子元素确切的尺寸,
               //這時就先取父容器給子元素留下的最大空間,模式為AT_MOST,表示子元素不能超過該最大值
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子元素的尺寸隻希望能包裹自身的内容就可以了,這時子元素還沒測量,無法知道具體尺寸,
                // 就先取父容器給子元素留下的最大空間,模式為AT_MOST,表示子元素不能超過該最大值
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父容器沒有對子元素的大小進行限制
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // 子元素指定了大小,就取子元素的尺寸,模式為EXACTLY,表明該子元素确定了尺寸
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 子元素想要占滿父容器,先判斷下子元素是否需要取0,
                // 如果不需要取0,就先取父容器給子元素留下的最大空間,模式為UNSPECIFIED,表示子元素并沒有受到限制
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子元素的尺寸隻希望能包裹自身的内容就可以了,判斷下需不需要取0,
                // 如果不需要就先取父容器給子元素留下的最大空間,模式為UNSPECIFIED,表示子元素并沒有受到限制
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //根據得到的大小和模式傳回一個MeasureSpec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
           

getChildMeasureSpec()

這個方法清楚展示了普通View的MeasureSpec的建立規則,每個View的MeasureSpec狀态量由其直接父View的MeasureSpec和View自身的屬性LayoutParams(LayoutParams有寬高尺寸值等資訊)共同決定。總結為下表:

View的布局屬性\父ViewGroup的MeasurSpec EXACTLY AT_MOST UNSPECIFIED
非負具體值 EXACTLY childSize EXACTLY childSize EXACTLY childSize
match_parent EXACTLY parentSize AT_MOST parentSize UNSPECIFIED 0/parentSize
wrap_content AT_MOST parentSize AT_MOST parentSize UNSPECIFIED 0/parentSize

在得到View的MeasureSpec狀态後,将其與尺寸值通過

makeMeasureSpec(int size,int mode)

方法結合在一起,就是最終傳給View的

onMeasure(int, int)

方法的MeasureSpec值了。

檢視源碼發現,ViewGroup并沒有定義其測量的具體過程方法,這是因為ViewGroup是一個抽象類,其測量過程的

onMeasure()

方法需要各個子類去實作,比如LinearLayout、RelativeLayout、ListView等。為什麼ViewGroup不像View一樣對其

onMeasure()

方法做統一的實作呢?那是因為不同的ViewGroup子類有不同的布局特性,這導緻它們的測量細節各不相同,是以ViewGroup無法做統一的實作。

需要注意的是,雖然View實作了

onMeasure()

方法,但也隻是一種預設實作,前面也提到過View的這種預設實作是不區分

wrap_content

match_parent

的,而View的子類如果需要支援區分實作這兩種布局方式,就需要根據自身的特性自定義實作

onMeasure()

方法,比如TextView、ImageView等就都實作了

onMeasure()

方法,而且實作的方式各不相同,有興趣的同學可以去看下源碼,這裡就不細講了。

DecorView和ViewRootImpl

本來關于View的measure過程到這裡已經介紹得七七八八了,但是為了更好的了解整個View樹結構的測量過程,這裡就先簡單提下DecorView和ViewRootImpl這兩個家夥。

我們知道,Android界面上的View其實是一個View樹結構,而DecorView就是View樹的頂端,是視圖的頂級View,一般情況下它内部會包含一個豎直方向的LinearLayout,在這個LinearLayout裡面有上下兩個部分(具體情況和Android版本以及主題有關),上面是标題欄,下面是内容欄。我們在建立Activity時通過

setContentView()

添加的布局檔案其實就是被加到内容欄之中,而内容欄是一個id為content的FrameLayout,是以可以了解Activity指定布局的方法不叫

setView()

而叫

setContentView()

了吧。

Android源碼完全解析——View的Measure過程

每一個Activity元件都有一個關聯的Window對象,用來描述一個應用程式視窗。每一個應用程式視窗内部又包含有一個View對象,用來描述應用程式視窗的視圖。在Activity建立完畢後,DecorView會被添加到Window中,之後我們才能在螢幕上看到應用程式的視圖效果。

而ViewRootImpl是連接配接WindowManager和DecorView的紐帶,控件的測量、布局、繪制以及輸入事件的分發處理都由ViewRootImpl觸發。它是WindowManagerGlobal工作的實際實作者,是以它還需要負責與WMS互動通信以調整視窗的位置大小,以及對來自WMS的事件(如視窗尺寸改變等)作出相應的處理。它調用了一個

performTraversals()

方法使得View樹開始三大工作流程,然後使得View展現在我們面前。關鍵源碼如下:

private void performTraversals() {
            ...

        if (!mStopped || mReportNextDraw) {
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  // 1
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    
            ...
            }
        } 

        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
        }


        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            performDraw();
        }
        ...
}
           

我們看到它裡面執行了三個方法,分别是

performMeasure()

performLayout()

performDraw()

這三個方法,這三個方法分别完成DecorView的measure、layout、和draw這三大流程,其中

performMeasure()

中會調用

measure()

方法,在

measure()

方法中又會調用

onMeasure()

方法,在

onMeasure()

方法中會對所有子元素進行measure過程,這個時候measure流程就從父容器傳遞到子元素中了,這樣就完成了一次measure過程。接着子元素會重複父容器的measure過程,如此反複就實作了從DecorView開始對整個View樹的周遊測量,measure過程就這樣完成了。同理,

performLayout()

performDraw()

也是類似的傳遞流程。針對

performTraveals()

的大緻流程,可以用以下流程圖來表示。

Android源碼完全解析——View的Measure過程

performTraversals()

方法中,其實對于View樹的測量、布局、繪制不是簡單地依次單次執行,以上的流程圖隻是一個為了便于了解而簡化版的流程,真正的流程應該分為以下五個工作階段:

  • 預測量階段:這是進入

    performTraversals()

    方法後的第一個階段,它會對View樹進行第一次測量。在此階段中将會計算出View樹為顯示其内容所需的尺寸,即期望的視窗尺寸。(調用

    measureHierarchy()

  • 布局視窗階段:根據預測量的結果,通過

    IWindowSession.relayout()

    方法向WMS請求調整視窗的尺寸等屬性,這将引發WMS對視窗進行重新布局,并将布局結果傳回給ViewRootImpl。(調用

    relayoutWindow()

  • 最終測量階段:預測量的結果是View樹所期望的視窗尺寸。然而由于在WMS中影響視窗布局的因素很多,WMS不一定會将視窗準确地布局為View樹所要求的尺寸,而迫于WMS作為系統服務的強勢地位,View樹不得不接受WMS的布局結果。是以在這一階段,

    performTraversals()

    将以視窗的實際尺寸對View樹進行最終測量。(調用

    performMeasure()

  • 布局View樹階段:完成最終測量之後便可以對View樹進行布局了。(調用

    performLayout()

  • 繪制階段:這是performTraversals()的最終階段。确定了控件的位置與尺寸後,便可以對View樹進行繪制了。(調用

    performDraw()

也就是說,實際上多了預測量階段和布局視窗階段,這裡面還有很多可以講的,但本文主要是介紹View的measure過程,相關性不大的盡量少涉及,以免太過混亂。

預測量階段和最終測量階段都會至少完整測量一次View樹,這兩個階段的差別也隻是參數不同而已。預測量階段用到了一個

measureHierarchy()

方法,該方法傳入的參數desiredWindowWidth與desiredWindowHeight是期望的視窗尺寸。View樹本可以按照這兩個參數完成測量,但是

measureHierarchy()

有自己的考量,即如何将視窗布局地盡可能地優雅。

這是針對将LayoutParams.width設定為了WRAP_CONTENT的懸浮視窗而言。如前文所述,在設定為WRAP_CONTENT時,指定的desiredWindowWidth是應用可用的最大寬度,如此可能會産生下面左圖所示的醜陋布局。這種情況較容易發生在AlertDialog中,當AlertDialog需要顯示一條比較長的消息時,由于給予的寬度足夠大,是以它有可能将這條消息以一行顯示,并使得其視窗充滿了整個螢幕寬度,在橫屏模式下這種布局尤為醜陋。

倘若能夠對可用寬度進行适當的限制,迫使AlertDialog将消息換行顯示,則産生的布局結果将會優雅得多,如圖下面右圖所示。但是,倘若不厘清紅皂白地對寬度進行限制,當控件樹真正需要足夠的橫向空間時,會導緻内容無法顯示完全,或者無法達到最佳的顯示效果。例如當一個懸浮視窗希望盡可能大地顯示一張照片時就會出現這樣的情況。

Android源碼完全解析——View的Measure過程

那麼

measureHierarchy()

如何解決這個問呢?它采取了與View樹進行協商的辦法,即先使用

measureHierarchy()

所期望的寬度限制嘗試對View樹進行測量,然後通過測量結果來檢查View樹是否能夠在此限制下滿足其充分顯示内容的要求。倘若沒能滿足,則

measureHierarchy()

進行讓步,放寬對寬度的限制,然後再次進行測量,再做檢查。倘若仍不能滿足則再度進行讓步。

關鍵源碼如下:

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        // 表示測量結果是否可能導緻視窗的尺寸發生變化
        boolean windowSizeMayChange = false;

        //goodMeasure表示了測量是否能滿足View樹充分顯示内容的要求
        boolean goodMeasure = false;
        //測量協商僅發生在LayoutParams.width被指定為WRAP_CONTENT的情況下
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            //第一次協商。measureHierarchy()使用它最期望的寬度限制進行測量。
            //這一寬度限制定義為一個系統資源。
            //可以在frameworks/base/core/res/res/values/config.xml找到它的定義
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            // 寬度限制被存放在baseSize中
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //第一次測量。調用performMeasure()進行測量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                
                //View樹的測量結果可以通過mView的getmeasuredWidthAndState()方法擷取。
                //View樹對這個測量結果不滿意,則會在傳回值中添加MEASURED_STATE_TOO_SMALL位
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;  // 控件樹對測量結果滿意,測量完成
                } else {
                  //第二次協商。上次的測量結果表明View樹認為measureHierarchy()給予的寬度太小,在此
                  //在此适當地放寬對寬度的限制,使用最大寬度與期望寬度的中間值作為寬度限制
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                  //第二次測量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                  // 再次檢查控件樹是否滿足此次測量
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                     // 控件樹對測量結果滿意,測量完成
                        goodMeasure = true;
                    }
                }
            }
        }
        
        if (!goodMeasure) {
          //最終測量。當View樹對上述兩次協商的結果都不滿意時,measureHierarchy()放棄所有限制
          //做最終測量。這一次将不再檢查控件樹是否滿意了,因為即便其不滿意,measurehierarchy()也沒
          //有更多的空間供其使用了
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          //如果測量結果與ViewRootImpl中目前的視窗尺寸不一緻,則表明随後可能有必要進行視窗尺寸的調整
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        // 傳回視窗尺寸是否可能需要發生變化
        return windowSizeMayChange;
    }
           

可以看到,

measureHierarchy()

方法最終也是調用了

performMeasure()

方法對View樹進行測量,隻是多了協商測量的過程。

顯然,對于非懸浮視窗,即當LayoutParams.width被設定為MATCH_PARENT時,不存在協商過程,直接使用給定的desiredWindowWidth/Height進行測量即可。而對于懸浮視窗,

measureHierarchy()

可以連續進行兩次讓步,進而導緻View的

onMeasure()

方法多次被調用。

這裡也看到,在View的measure過程中設定的MEASURED_STATE_TOO_SMALL标志位就在測量協商過程中起作用了。

Android源碼完全解析——View的Measure過程

總結

看到了這裡,我們發現Android中View的measure過程是很巧妙的,知道如何利用以前測量過的資料,如果情況有變,那麼就調用

onMeasure()

方法進行實際的測量工作。真正實作對View本身的測量就是在

onMeasure()

中,在該方法中View要根據父ViewGroup給其傳遞進來的widthMeasureSpec和heightMeasureSpec,并結合View自身想要的尺寸,綜合考慮,計算出最終的寬度和高度,并存儲到相應的成員變量中,這才标志着該View測量有效的完成了,如果沒有将值存入到成員變量中,View會抛出異常。而且在成員變量中還儲藏着測量的狀态資訊state,該資訊表示了View對此次測量的結果是否滿意。而這個state資訊有可能會在ViewRootImpl在做視窗大小決策的時候提供回報,進而達到最佳的顯示效果。

關于view的學習 我們有很多知識點 有需要的朋友可以根據開頭的view源碼分析技術大綱 查漏補缺  加入我們學習交流群:加入

點選檢視完整高清的Android進階課程大綱腦圖騰訊T3Android進階技術腦圖

繼續閱讀