View的繪制流程之Measure

View的繪制流程(一)

每一個視圖的繪制過程都必須經歷三個最主要的階段瘾敢,即onMeasure()垒棋、onLayout()和onDraw()

Measure

知識點

1. MeasureSpec

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

MeasureSpec是一個32位的int值带猴,其中高2位為測量模式荚斯,低30位為測量的大小惩猫。

作用:一個MeasureSpec封裝了從父容器傳遞給子容器的布局要求。
含義:MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通過簡單的計算得出一個針對子View的測量要求猬仁,這個測量要求就是MeasureSpec帝璧。

  • MeasureSpec的三種測量模式

UPSPECIFIED : 父容器對于子容器沒有任何限制,子容器想要多大就多大。
EXACTLY: 父容器已經為子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間:當我們的控件layout_width(Height)屬性指定為具體的值或者是match_parent時湿刽。
AT_MOST:子容器可以是聲明大小內的任意大械乃浮:當我們的控件layout_width(Height)屬性指定為wrap_content時。

2. ViewRootImpl

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

View的Measure過程

View測量前的準備工作 ---> MeasureSpec

在ViewRoot的performTraversals()方法中震叮,調用子View的measure()方法。measure()方法接收兩個參數(shù)鳍鸵,widthMeasureSpec和heightMeasureSpec,這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小尉间。雖然這兩個參數(shù)從父View傳遞過來偿乖,是父View的MeasureSpec和子View自己的LayoutParams共同決定的击罪,而子View的LayoutParams其實就是我們在xml寫的時候設置的layout_width和layout_height轉化而來的。(父View的Measure過程會等子View測量之后再對自己進行測量)贪薪。
舉個例子:傳遞給子View的MeasureSpc的計算是通過調用measureChildWithMargins()來計算的


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

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

//根據(jù)父View的測量規(guī)格和父View自己的Padding画切,
//還有子View的Margin和已經用掉的空間大锌⒒(widthUsed),就能算出子View的MeasureSpec,具體計算  
過程看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的計算毫别,算出子View的MeasureSpec,然后父  
容器傳遞給子容器的
// 然后讓子View用這個MeasureSpec(一個測量要求典格,比如不能超過多大)去測量自己岛宦,如果子View是ViewGroup 那還會遞歸往下測量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

// spec參數(shù)   表示父View的MeasureSpec 
// padding參數(shù)    父View的Padding+子View的Margin耍缴,父View的大小減去這些邊距砾肺,才能精確算出
//               子View的MeasureSpec的size
// childDimension參數(shù)  表示該子View內部LayoutParams屬性的值(lp.width或者lp.height)  
//                    可以是wrap_content、match_parent防嗡、一個精確指(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;    //初始化值蚁趁,最后通過這個兩個值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值裙盾,最后通過這個兩個值生成子View的MeasureSpec

    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1、父View是EXACTLY的 荣德!  
    case MeasureSpec.EXACTLY:   
        //1.1闷煤、子View的width或height是個精確值 (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是個精確值 (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是個精確值 (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構建MeasureSpec對象减宣。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}

DecorView的widthMeasureSpec和heightMeasureSpec是從ViewRootImpl中傳遞來的。也是類似的計算方法玩荠∑犭纾可以參考郭神的博客

MeasureSpec的計算流程大致就是:通過父View的MeasureSpec和子View的LayoutParams來計算出子View的計算標準MeasureSpec,并傳遞給子View的measure()方法讓子View測量自己和自己子View的尺寸阶冈。

下面介紹幾個常見的情況:

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

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

    1. 如果此時子View的布局文件中屬性layout_width/height等于match_parent具被,由于父View的大小無法確定玻募,所以無法確子View的大小。此時返回的MeasureSpec的mode變成了AT_MOST一姿,size是父View傳遞過來的MeasureSpec的size的值七咧,代表最大不能超過這個值。
    2. 如果此時子View的布局文件中屬性layout_width/height等于wrap_content叮叹,由于父View的大小無法確定的同時艾栋,在沒有計算出子View的content的大小是無法確定子View的大小。所以傳遞給子View的MeasureSpec的mode是AT_MOST蛉顽,size就是父View中傳遞來的MeasureSpec中的size蝗砾。代表大小暫定這個值,最大不能超過這個值。
    3. 如果此時子View的布局文件中屬性layout_width/height等于特定的值遥诉,那么傳遞給子View的MeasureSpec的mode是EXACTLY拇泣,size是指定的值。
  • 如果父View的MeasureSpec的模式是UPSPECIFIED矮锈,表示沒有任何束縛和約束,不像AT_MOST表示最大只能多大睁蕾,不也像EXACTLY表示父View確定的大小苞笨,子View可以得到任意想要的大小,不受約束子眶。

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

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

當我們計算好子View的計算標準MeasureSpec時,調用子View的measure()方法磁奖。

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

源碼中囊拜,View.measure()的方法是final,所以無法覆寫比搭。View的測試工作在onMeasure()方法中完成冠跷。要想自定義View的測量,必須重寫onMeasure()方法身诺。

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

在onMeasure()方法中蜜托,調用了setMeasuredDimension()。這個方法可以簡單理解就是給mMeasuredWidth和mMeasuredHeight設值霉赡,如果這兩個值一旦設置了橄务,那么意味著對于這個View的測量結束了,這個View的寬高已經有測量的結果出來了同廉。

注意:setMeasuredDimension()方法一定要在onMeasure()方法中調用仪糖,否則會拋出異常。

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

getDefaultSize()的size參數(shù)由getSuggestedMinimumWidth()返回锅劝,這個值由布局文件中的android:minHeight/width和View的background決定尺寸共同決定。這個size就是該view的默認大小蟆湖。默認的測試方法(getDefaultSize())中故爵,當MeasureSpec的mode不是UNSPECIFIED時,都將MeasureSpec中的size返回。
相反在一些View的派生類中(TextView诬垂、Button劲室、ImageView等),都是對onMeasure()方法重寫结窘。如果該View(TextView)的MeasureSpec的mode是AT_MOST很洋,會用其content進行計算后的值和MeasureSpec中的size做比較,取小的一個作為子View(TextView)的尺寸隧枫。

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

ViewGroup 類并沒有實現(xiàn)onMeasure喉磁。當ViewGroup沒有確定的值時(wrap_content),需要對其子View進行遍歷官脓,并獲取所有子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);  
        }  
    }  
}

首先調用上述方法卑笨,遍歷所有的子View孕暇,然后調用下面的方法逐個進行測量

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

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

//FrameLayout 的測量
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的都會參與測量搀缠,measureChildWithMargins方法在最上面
    // 的源碼已經講過了铛楣,如果忘了回頭去看看,基本思想就是父View把自己的MeasureSpec 
    // 傳給子View結合子View自己的LayoutParams 算出子View 的MeasureSpec艺普,然后繼續(xù)往下傳簸州,
    // 傳遞葉子節(jié)點,葉子節(jié)點沒有子View歧譬,根據(jù)傳下來的這個MeasureSpec測量自己就好了岸浑。
     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);  
     ....
     ....
   }
}
.....
.....
//所有的孩子測量之后,經過一系類的計算之后通過setMeasuredDimension設置自己的寬高瑰步,
//對于FrameLayout 可能用最大的字View的大小矢洲,對于LinearLayout,可能是高度的累加缩焦,
//具體測量的原理去看看源碼读虏。總的來說袁滥,父View是等所有的子View測量結束之后盖桥,再來測量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}

這里通過深度優(yōu)先遍歷來完成所有子View的測量题翻,然后再對自身進行測量揩徊。

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市齿税,隨后出現(xiàn)的幾起案子彼硫,更是在濱河造成了極大的恐慌,老刑警劉巖凌箕,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乌助,死亡現(xiàn)場離奇詭異,居然都是意外死亡陌知,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門掖肋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仆葡,“玉大人,你說我怎么就攤上這事志笼⊙刂眩” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵纫溃,是天一觀的道長腰涧。 經常有香客問我,道長紊浩,這世上最難降的妖魔是什么窖铡? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮坊谁,結果婚禮上费彼,老公的妹妹穿的比我還像新娘。我一直安慰自己口芍,他們只是感情好箍铲,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鬓椭,像睡著了一般颠猴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上小染,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天翘瓮,我揣著相機與錄音,去河邊找鬼氧映。 笑死春畔,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播律姨,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼振峻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了择份?” 一聲冷哼從身側響起扣孟,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荣赶,沒想到半個月后凤价,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡拔创,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年利诺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剩燥。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡慢逾,死狀恐怖,靈堂內的尸體忽然破棺而出灭红,到底是詐尸還是另有隱情侣滩,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布变擒,位于F島的核電站君珠,受9級特大地震影響,放射性物質發(fā)生泄漏娇斑。R本人自食惡果不足惜策添,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悠菜。 院中可真熱鬧舰攒,春花似錦、人聲如沸悔醋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芬骄。三九已至猾愿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間账阻,已是汗流浹背蒂秘。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淘太,地道東北人姻僧。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓规丽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撇贺。 傳聞我的和親對象是個殘疾皇子赌莺,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容