View的繪制流程之Measure

View的繪制流程(一)

每一個(gè)視圖的繪制過程都必須經(jīng)歷三個(gè)最主要的階段,即onMeasure()、onLayout()和onDraw()

Measure

知識(shí)點(diǎn)

1. MeasureSpec

  • 我們通過Android系統(tǒng)的MeasureSpec類來測(cè)量view。

MeasureSpec是一個(gè)32位的int值,其中高2位為測(cè)量模式,低30位為測(cè)量的大小。

作用:一個(gè)MeasureSpec封裝了從父容器傳遞給子容器的布局要求。
含義:MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通過簡(jiǎn)單的計(jì)算得出一個(gè)針對(duì)子View的測(cè)量要求,這個(gè)測(cè)量要求就是MeasureSpec。

  • MeasureSpec的三種測(cè)量模式

UPSPECIFIED : 父容器對(duì)于子容器沒有任何限制,子容器想要多大就多大。
EXACTLY: 父容器已經(jīng)為子容器設(shè)置了尺寸,子容器應(yīng)當(dāng)服從這些邊界,不論子容器想要多大的空間:當(dāng)我們的控件layout_width(Height)屬性指定為具體的值或者是match_parent時(shí)。
AT_MOST:子容器可以是聲明大小內(nèi)的任意大?。寒?dāng)我們的控件layout_width(Height)屬性指定為wrap_content時(shí)。

2. ViewRootImpl

每個(gè)Activity都包含一個(gè)Window對(duì)象,在Android中Window對(duì)象通常由PhoneWindow來實(shí)現(xiàn)(是Activity和整個(gè)View系統(tǒng)交互的接口,每個(gè)Window都對(duì)應(yīng)著一個(gè)View和一個(gè)ViewRootImpl,Window(Window無法直接訪問,要通過WindowManager)和View通過ViewRootImpl來建立聯(lián)系,執(zhí)行添加,刪除,更新View等操作)。PhoneWindow將一個(gè)DecorView設(shè)置為整個(gè)應(yīng)用窗口的根View。DecorView將要顯示的具體內(nèi)容呈現(xiàn)在了PhoneWindow上。所以繪制的入口是由ViewRootImpl的performTraversals方法來發(fā)起Measure,Layout,Draw等流程的。

View的Measure過程

View測(cè)量前的準(zhǔn)備工作 ---> MeasureSpec

在ViewRoot的performTraversals()方法中,調(diào)用子View的measure()方法。measure()方法接收兩個(gè)參數(shù),widthMeasureSpec和heightMeasureSpec,這兩個(gè)值分別用于確定視圖的寬度和高度的規(guī)格和大小。雖然這兩個(gè)參數(shù)從父View傳遞過來,是父View的MeasureSpec和子View自己的LayoutParams共同決定的,而子View的LayoutParams其實(shí)就是我們?cè)趚ml寫的時(shí)候設(shè)置的layout_width和layout_height轉(zhuǎn)化而來的。(父View的Measure過程會(huì)等子View測(cè)量之后再對(duì)自己進(jìn)行測(cè)量)。
舉個(gè)例子:傳遞給子View的MeasureSpc的計(jì)算是通過調(diào)用measureChildWithMargins()來計(jì)算的


protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最后都會(huì)封裝到這個(gè)個(gè)LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

//根據(jù)父View的測(cè)量規(guī)格和父View自己的Padding,
//還有子View的Margin和已經(jīng)用掉的空間大?。╳idthUsed),就能算出子View的MeasureSpec,具體計(jì)算  
過程看getChildMeasureSpec方法。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);    

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,           
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  + heightUsed, lp.height);  

//通過父View的MeasureSpec和子View的自己LayoutParams的計(jì)算,算出子View的MeasureSpec,然后父  
容器傳遞給子容器的
// 然后讓子View用這個(gè)MeasureSpec(一個(gè)測(cè)量要求,比如不能超過多大)去測(cè)量自己,如果子View是ViewGroup 那還會(huì)遞歸往下測(cè)量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

// spec參數(shù)   表示父View的MeasureSpec 
// padding參數(shù)    父View的Padding+子View的Margin,父View的大小減去這些邊距,才能精確算出
//               子View的MeasureSpec的size
// childDimension參數(shù)  表示該子View內(nèi)部LayoutParams屬性的值(lp.width或者lp.height)  
//                    可以是wrap_content、match_parent、一個(gè)精確指(an exactly size),  
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //獲得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //獲得父View的大小  

   //父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小。
    int size = Math.max(0, specSize - padding);   

    int resultSize = 0;    //初始化值,最后通過這個(gè)兩個(gè)值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值,最后通過這個(gè)兩個(gè)值生成子View的MeasureSpec

    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1、父View是EXACTLY的 !  
    case MeasureSpec.EXACTLY:   
        //1.1、子View的width或height是個(gè)精確值 (an exactly size)  
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size為精確值  
            resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。  
        }   
        //1.2、子View的width或height為 MATCH_PARENT/FILL_PARENT   
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size. So be it.  
            resultSize = size;                   //size為父視圖大小  
            resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。  
        }   
        //1.3、子View的width或height為 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                   //size為父視圖大小  
            resultMode = MeasureSpec.AT_MOST;    //mode為AT_MOST 。  
        }  
        break;  

    // Parent has imposed a maximum size on us  
    //2、父View是AT_MOST的 !      
    case MeasureSpec.AT_MOST:  
        //2.1、子View的width或height是個(gè)精確值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... so be it  
            resultSize = childDimension;        //size為精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY 。  
        }  
        //2.2、子View的width或height為 MATCH_PARENT/FILL_PARENT  
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size, but our size is not fixed.  
            // Constrain child to not be bigger than us.  
            resultSize = size;                  //size為父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST  
        }  
        //2.3、子View的width或height為 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                  //size為父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST  
        }  
        break;  

    // Parent asked to see how big we want to be  
    //3、父View是UNSPECIFIED的 !  
    case MeasureSpec.UNSPECIFIED:  
        //3.1、子View的width或height是個(gè)精確值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... let him have it  
            resultSize = childDimension;        //size為精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY  
        }  
        //3.2、子View的width或height為 MATCH_PARENT/FILL_PARENT  
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size... find out how big it should  
            // be  
            resultSize = 0;                        //size為0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED  
        }   
        //3.3、子View的width或height為 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size.... find out how  
            // big it should be  
            resultSize = 0;                        //size為0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED  
        }  
        break;  
    }  
    //根據(jù)上面邏輯條件獲取的mode和size構(gòu)建MeasureSpec對(duì)象。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}

DecorView的widthMeasureSpec和heightMeasureSpec是從ViewRootImpl中傳遞來的。也是類似的計(jì)算方法??梢詤⒖?a target="_blank" rel="nofollow">郭神的博客

MeasureSpec的計(jì)算流程大致就是:通過父View的MeasureSpec和子View的LayoutParams來計(jì)算出子View的計(jì)算標(biāo)準(zhǔn)MeasureSpec,并傳遞給子View的measure()方法讓子View測(cè)量自己和自己子View的尺寸。

下面介紹幾個(gè)常見的情況:

  • 如果父View的MeasureSpec的模式是EXACTLY,那么父View的MeasureSpec.size就是確定大小的

    1. 如果此時(shí)子View的布局文件中屬性layout_width/height等于match_parent(指定的值),那么子View的Size就是父View的size(在布局文件中給定的值)。
    2. 如果此時(shí)子View的布局文件中屬性layout_width/height等于wrap_content,那么子View的大小需要根據(jù)自己的content來確定,但是最終的大小不能超過父View的size。子View首先通過遍歷調(diào)用自身的子View并分別調(diào)用他們的measure()方法計(jì)算他們的尺寸,然后再計(jì)算自身的大小。而此時(shí)傳遞給自身子View的MeasureSpec參數(shù)中mode為AT_MOST,size暫定為父View的size。表示的意思就是自身的大小沒有不確切的值,其子View的大小最大為給定的MeasureSpec的大小,不能超過它(這就是AT_MOST 的意思)。
  • 如果父View的MeasureSpec的模式是AT_MOST,說明父View的大小是不確定,最大的大小是MeasureSpec 的size值,不能超過這個(gè)值。

    1. 如果此時(shí)子View的布局文件中屬性layout_width/height等于match_parent,由于父View的大小無法確定,所以無法確子View的大小。此時(shí)返回的MeasureSpec的mode變成了AT_MOST,size是父View傳遞過來的MeasureSpec的size的值,代表最大不能超過這個(gè)值。
    2. 如果此時(shí)子View的布局文件中屬性layout_width/height等于wrap_content,由于父View的大小無法確定的同時(shí),在沒有計(jì)算出子View的content的大小是無法確定子View的大小。所以傳遞給子View的MeasureSpec的mode是AT_MOST,size就是父View中傳遞來的MeasureSpec中的size。代表大小暫定這個(gè)值,最大不能超過這個(gè)值。
    3. 如果此時(shí)子View的布局文件中屬性layout_width/height等于特定的值,那么傳遞給子View的MeasureSpec的mode是EXACTLY,size是指定的值。
  • 如果父View的MeasureSpec的模式是UPSPECIFIED,表示沒有任何束縛和約束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View確定的大小,子View可以得到任意想要的大小,不受約束。

    1. 如果此時(shí)子View的布局文件中屬性layout_width/height等于match_parent/wrap_content,由于父View的大小沒有任何約束,所以子View的大小也就沒有任何約束,此時(shí)傳遞給子View的MeasureSpec的mode是UPSPECIFIED,size是0。
    2. 如果此時(shí)子View的布局文件中屬性layout_width/height等于特定的值,那么傳遞給子View的MeasureSpec的mode是EXACTLY,size是指定的值。

View測(cè)量過程 ---> measure()方法

當(dāng)我們計(jì)算好子View的計(jì)算標(biāo)準(zhǔn)MeasureSpec時(shí),調(diào)用子View的measure()方法。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  onMeasure(widthMeasureSpec,heightMeasureSpec);
  .....
}

源碼中,View.measure()的方法是final,所以無法覆寫。View的測(cè)試工作在onMeasure()方法中完成。要想自定義View的測(cè)量,必須重寫onMeasure()方法。

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

在onMeasure()方法中,調(diào)用了setMeasuredDimension()。這個(gè)方法可以簡(jiǎn)單理解就是給mMeasuredWidth和mMeasuredHeight設(shè)值,如果這兩個(gè)值一旦設(shè)置了,那么意味著對(duì)于這個(gè)View的測(cè)量結(jié)束了,這個(gè)View的寬高已經(jīng)有測(cè)量的結(jié)果出來了。

注意:setMeasuredDimension()方法一定要在onMeasure()方法中調(diào)用,否則會(huì)拋出異常。

//獲取的是android:minHeight屬性的值或者View背景圖片的大小值
protected int getSuggestedMinimumWidth() { 
   return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); 
} 
//@param size參數(shù)一般表示設(shè)置了android:minHeight屬性或者該View背景圖片的大小值  
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:        //表示該View的大小父視圖未定,設(shè)置為默認(rèn)值 
     result = size;  
     break;    
   case MeasureSpec.AT_MOST:    
   case MeasureSpec.EXACTLY:        
     result = specSize;  
     break;   
 }    
return result;
}

getDefaultSize()的size參數(shù)由getSuggestedMinimumWidth()返回,這個(gè)值由布局文件中的android:minHeight/width和View的background決定尺寸共同決定。這個(gè)size就是該view的默認(rèn)大小。默認(rèn)的測(cè)試方法(getDefaultSize())中,當(dāng)MeasureSpec的mode不是UNSPECIFIED時(shí),都將MeasureSpec中的size返回。
相反在一些View的派生類中(TextView、Button、ImageView等),都是對(duì)onMeasure()方法重寫。如果該View(TextView)的MeasureSpec的mode是AT_MOST,會(huì)用其content進(jìn)行計(jì)算后的值和MeasureSpec中的size做比較,取小的一個(gè)作為子View(TextView)的尺寸。

ViewGroup測(cè)試過程--->onMeasure()

ViewGroup 類并沒有實(shí)現(xiàn)onMeasure。當(dāng)ViewGroup沒有確定的值時(shí)(wrap_content),需要對(duì)其子View進(jìn)行遍歷,并獲取所有子View的大小后,再來決定自己的大小。

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];  
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {  
            measureChild(child, widthMeasureSpec, heightMeasureSpec);  
        }  
    }  
}

首先調(diào)用上述方法,遍歷所有的子View,然后調(diào)用下面的方法逐個(gè)進(jìn)行測(cè)量

protected void measureChild(View child, int parentWidthMeasureSpec,  
        int parentHeightMeasureSpec) {  
    final LayoutParams lp = child.getLayoutParams();  
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
            mPaddingLeft + mPaddingRight, lp.width);  
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
            mPaddingTop + mPaddingBottom, lp.height);  
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
}  

只有當(dāng)所有子View測(cè)量完畢后,才去計(jì)算ViewGroup的尺寸(無論ViewGroup的layout_width/height屬性是什么)。
但是測(cè)量的工作實(shí)在onMeasure()中完成的,那么來看下FrameLayout是如何實(shí)現(xiàn)onMeasure()方法

//FrameLayout 的測(cè)量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {    
   final View child = getChildAt(i);    
   if (mMeasureAllChildren || child.getVisibility() != GONE) {   
    // 遍歷自己的子View,只要不是GONE的都會(huì)參與測(cè)量,measureChildWithMargins方法在最上面
    // 的源碼已經(jīng)講過了,如果忘了回頭去看看,基本思想就是父View把自己的MeasureSpec 
    // 傳給子View結(jié)合子View自己的LayoutParams 算出子View 的MeasureSpec,然后繼續(xù)往下傳,
    // 傳遞葉子節(jié)點(diǎn),葉子節(jié)點(diǎn)沒有子View,根據(jù)傳下來的這個(gè)MeasureSpec測(cè)量自己就好了。
     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
     final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
     maxWidth = Math.max(maxWidth, child.getMeasuredWidth() +  lp.leftMargin + lp.rightMargin);        
     maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);  
     ....
     ....
   }
}
.....
.....
//所有的孩子測(cè)量之后,經(jīng)過一系類的計(jì)算之后通過setMeasuredDimension設(shè)置自己的寬高,
//對(duì)于FrameLayout 可能用最大的字View的大小,對(duì)于LinearLayout,可能是高度的累加,
//具體測(cè)量的原理去看看源碼??偟膩碚f,父View是等所有的子View測(cè)量結(jié)束之后,再來測(cè)量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}

這里通過深度優(yōu)先遍歷來完成所有子View的測(cè)量,然后再對(duì)自身進(jìn)行測(cè)量。

注意:在setMeasuredDimension()方法調(diào)用之后,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測(cè)量出的寬高,以此之前調(diào)用這兩個(gè)方法得到的值都會(huì)是0。

參考文章:郭神的博客,Kelin大牛

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容