Android中measure過程、WRAP_CONTENT詳解以及xml佈局文件解析流程淺析(下

            

                                                                            本文原創, 轉載請註明出處http://blog.csdn.net/qinjuninghtml



        上篇文章<<Android中measure過程、WRAP_CONTENT詳解以及xml佈局文件解析流程淺析(上)>>中,咱們java

  瞭解了View樹的轉換過程以及如何設置View的LayoutParams的。本文繼續沿着既定軌跡繼續未完成的job。android

        主要知識點以下:
                 一、MeasureSpc類說明
                 二、measure過程詳解(揭祕其細節);
                 三、root View被添加至窗口時,UI框架是如何設置其LayoutParams值得。

程序員

       在講解measure過程前,咱們很是有必要理解MeasureSpc類的使用,不然理解起來也只能算是囫圇吞棗。數組


 一、MeasureSpc類說明


   1.1  SDK 說明以下

              A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec

         represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and架構

         a mode. 

        即:
             MeasureSpc類封裝了父View傳遞給子View的佈局(layout)要求。每一個MeasureSpc實例表明寬度或者高度
app

   (只能是其一)要求。 它有三種模式:框架

            ①、UNSPECIFIED(未指定),父元素部隊自元素施加任何束縛,子元素能夠獲得任意想要的大小;異步

            ②、EXACTLY(徹底),父元素決定自元素的確切大小,子元素將被限定在給定的邊界裏而忽略它自己大小;ide

            ③、AT_MOST(至多),子元素至多達到指定大小的值。


   經常使用的三個函數:

  static int getMode(int measureSpec)  :  根據提供的測量值(格式)提取模式(上述三個模式之一)

     static int getSize(int measureSpec)  : 根據提供的測量值(格式)提取大小值(這個大小也就是咱們一般所說的大小)

     static int makeMeasureSpec(int size,int mode)  :  根據提供的大小值和模式建立一個測量值(格式)


             以上摘取自:  <<
MeasureSpec介紹及使用詳解>>

   1.2   MeasureSpc類源碼分析   其爲View.java類的內部類,路徑:\frameworks\base\core\java\android\view\View.java

[java]  view plain copy print ?
  1. public class View implements ... {  
  2.      ...  
  3.      public static class MeasureSpec {  
  4.         private static final int MODE_SHIFT = 30//移位位數爲30  
  5.         //int類型佔32位,向右移位30位,該屬性表示掩碼值,用來與size和mode進行"&"運算,獲取對應值。  
  6.         private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
  7.   
  8.         //向右移位30位,其值爲00 + (30位0)  , 即 0x0000(16進製表示)  
  9.         public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
  10.         //向右移位30位,其值爲01 + (30位0)  , 即0x1000(16進製表示)  
  11.         public static final int EXACTLY     = 1 << MODE_SHIFT;  
  12.         //向右移位30位,其值爲02 + (30位0)  , 即0x2000(16進製表示)  
  13.         public static final int AT_MOST     = 2 << MODE_SHIFT;  
  14.   
  15.         //建立一個整形值,其高兩位表明mode類型,其他30位表明長或寬的實際值。能夠是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size  
  16.         public static int makeMeasureSpec(int size, int mode) {  
  17.             return size + mode;  
  18.         }  
  19.         //獲取模式  ,與運算  
  20.         public static int getMode(int measureSpec) {  
  21.             return (measureSpec & MODE_MASK);  
  22.         }  
  23.         //獲取長或寬的實際值 ,與運算  
  24.         public static int getSize(int measureSpec) {  
  25.             return (measureSpec & ~MODE_MASK);  
  26.         }  
  27.   
  28.     }  
  29.     ...  
  30. }  

     MeasureSpec類的處理思路是:

      ①、右移運算,使int 類型的高兩位表示模式的實際值,其他30位表示其他30位表明長或寬的實際值----能夠是

         WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。


      ②、經過掩碼MODE_MASK進行與運算 「&」,取得模式(mode)以及長或寬(value)的實際值。


 二、measure過程詳解

 
   2.1  measure過程深刻分析


       以前的一篇博文<< Android中View繪製流程以及invalidate()等相關方法分析>>,咱們從」二B程序員」的角度簡單    解了measure過程的調用過程。過了這麼多,咱們也該升級了,- - 。如今請開始從」普通程序員」角度去理解這個

 過程。咱們重點查看measure過程當中地相關方法。

     咱們說過,當UI框架開始繪製時,皆是從ViewRoot.java類開始繪製的。


      ViewRoot類簡要說明: 任何顯示在設備中的窗口,例如:Activity、Dialog等,都包含一個ViewRoot實例,該

  類主要用來與遠端 WindowManagerService交互以及控制(開始/銷燬)繪製。


     Step 1、 開始UI繪製 , 具體繪製方法則是:

[java]  view plain copy print ?
  1. 路徑:\frameworks\base\core\java\android\view\ViewRoot.java  
  2. public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {  
  3.     ...  
  4.     //mView對象指添加至窗口的root View ,對Activity窗口而言,則是DecorView對象。  
  5.     View mView;      
  6.       
  7.     //開始View繪製流程  
  8.     private void performTraversals(){  
  9.         ...  
  10.         //這兩個值咱們在後面討論時,在回過頭來看看是怎麼賦值的。如今只須要記住其值MeasureSpec.makeMeasureSpec()構建的。  
  11.         int childWidthMeasureSpec; //其值由MeasureSpec類構建 , makeMeasureSpec  
  12.         int childHeightMeasureSpec;//其值由MeasureSpec類構建 , makeMeasureSpec  
  13.           
  14.   
  15.         // Ask host how big it wants to be  
  16.         host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  17.         ...  
  18.     }  
  19.     ...  
  20. }  

   
      這兒,我並無說出childWidthMeasureSpec和childHeightMeasureSpec類的來由(爲了不額外地開銷,等到
 第三部分時咱們在來攻克它,如今只須要記住其值MeasureSpec.makeMeasureSpec()構建的。

    Step 2 、調用measure()方法去作一些前期準備

       measure()方法原型定義在View.java類中,final修飾符修飾,其不能被重載:

    

[java]  view plain copy print ?
  1. public class View implements ... {  
  2.     ...  
  3.     /** 
  4.      * This is called to find out how big a view should be. The parent 
  5.      * supplies constraint information in the width and height parameters. 
  6.      * 
  7.      * @param widthMeasureSpec Horizontal space requirements as imposed by the 
  8.      *        parent 
  9.      * @param heightMeasureSpec Vertical space requirements as imposed by the 
  10.      *        parent 
  11.      * @see #onMeasure(int, int) 
  12.      */  
  13.     public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  14.         //判斷是否爲強制佈局,即帶有「FORCE_LAYOUT」標記 以及 widthMeasureSpec或heightMeasureSpec發生了改變  
  15.         if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  16.                 widthMeasureSpec != mOldWidthMeasureSpec ||  
  17.                 heightMeasureSpec != mOldHeightMeasureSpec) {  
  18.   
  19.             // first clears the measured dimension flag  
  20.             //清除MEASURED_DIMENSION_SET標記   ,該標記會在onMeasure()方法後被設置  
  21.             mPrivateFlags &= ~MEASURED_DIMENSION_SET;   
  22.   
  23.             // measure ourselves, this should set the measured dimension flag back  
  24.             // 一、 測量該View自己的大小 ; 2 、 設置MEASURED_DIMENSION_SET標記,不然接寫來會報異常。  
  25.             onMeasure(widthMeasureSpec, heightMeasureSpec);  
  26.   
  27.             // flag not set, setMeasuredDimension() was not invoked, we raise  
  28.             // an exception to warn the developer  
  29.             if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  30.                 throw new IllegalStateException("onMeasure() did not set the"  
  31.                         + " measured dimension by calling" + " setMeasuredDimension()");  
  32.             }  
  33.   
  34.             mPrivateFlags |= LAYOUT_REQUIRED;  //下一步是layout了,添加LAYOUT_REQUIRED標記  
  35.         }  
  36.   
  37.         mOldWidthMeasureSpec = widthMeasureSpec;   //保存值  
  38.         mOldHeightMeasureSpec = heightMeasureSpec; //保存值  
  39.     }  
  40.     ...  
  41. }  


      參數widthMeasureSpec和heightMeasureSpec 由父View構建,表示父View給子View的測量要求。其值地構建

 會在下面步驟中詳解。  

   measure()方法顯示判斷是否須要從新調用設置改View大小,即調用onMeasure()方法,而後操做兩個標識符:

            ①、重置MEASURED_DIMENSION_SET   : onMeasure()方法中,須要添加該標識符,不然,會報異常;    

       ②、添加LAYOUT_REQUIRED : 表示須要進行layout操做。

    最後,保存當前的widthMeasureSpec和heightMeasureSpec值。


   Step 3 、調用onMeasure()方法去真正設置View的長寬值,其默認實現爲:

[java]  view plain copy print ?
  1. /** 
  2.    * Measure the view and its content to determine the measured width and the 
  3.    * measured height. This method is invoked by {@link #measure(int, int)} and 
  4.    * should be overriden by subclasses to provide accurate and efficient 
  5.    * measurement of their contents. 
  6.    *  
  7.    * @param widthMeasureSpec horizontal space requirements as imposed by the parent. 
  8.    *                         The requirements are encoded with 
  9.    * @param heightMeasureSpec vertical space requirements as imposed by the parent. 
  10.    *                         The requirements are encoded with 
  11.    */  
  12.   //設置該View自己地大小  
  13.   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  14.       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  15.               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  16.   }  
  17.     
  18.   /** 
  19.    * Utility to return a default size. Uses the supplied size if the 
  20.    * MeasureSpec imposed no contraints. Will get larger if allowed 
  21.    * by the MeasureSpec. 
  22.    * 
  23.    * @param size Default size for this view 
  24.    * @param measureSpec Constraints imposed by the parent 
  25.    * @return The size this view should be. 
  26.    */  
  27.   //@param size參數通常表示設置了android:minHeight屬性或者該View背景圖片的大小值  
  28.   public static int getDefaultSize(int size, int measureSpec) {  
  29.       int result = size;    
  30.       int specMode = MeasureSpec.getMode(measureSpec);  
  31.       int specSize =  MeasureSpec.getSize(measureSpec);  
  32.   
  33.       //根據不一樣的mode值,取得寬和高的實際值。  
  34.       switch (specMode) {  
  35.       case MeasureSpec.UNSPECIFIED:  //表示該View的大小父視圖未定,設置爲默認值  
  36.           result = size;  
  37.           break;  
  38.       case MeasureSpec.AT_MOST:      //表示該View的大小由父視圖指定了  
  39.       case MeasureSpec.EXACTLY:  
  40.           result = specSize;  
  41.           break;  
  42.       }  
  43.       return result;  
  44.   }  
  45.   //得到設置了android:minHeight屬性或者該View背景圖片的大小值, 最爲該View的參考值  
  46.   protected int getSuggestedMinimumWidth() {  
  47.       int suggestedMinWidth = mMinWidth;  //  android:minHeight  
  48.   
  49.       if (mBGDrawable != null) { // 背景圖片對應地Width。  
  50.           final int bgMinWidth = mBGDrawable.getMinimumWidth();  
  51.           if (suggestedMinWidth < bgMinWidth) {  
  52.               suggestedMinWidth = bgMinWidth;  
  53.           }  
  54.       }  
  55.   
  56.       return suggestedMinWidth;  
  57.   }  
  58.   //設置View在measure過程當中寬和高  
  59.   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  60.       mMeasuredWidth = measuredWidth;  
  61.       mMeasuredHeight = measuredHeight;  
  62.   
  63.       mPrivateFlags |= MEASURED_DIMENSION_SET;  //設置了MEASURED_DIMENSION_SET標記  
  64.   }  

       主要功能就是根據該View屬性(android:minWidth和背景圖片大小)和父View對該子View的"測量要求",設置該      View的 mMeasuredWidth 和 mMeasuredHeight 值。


       這兒只是通常的View類型地實現方法。通常來講,父View,也就是ViewGroup類型,都須要在重寫onMeasure()   方法,遍歷全部子View,設置每一個子View的大小。基本思想以下:遍歷全部子View,設置每一個子View的大小。僞

  代碼錶示爲:

[java]  view plain copy print ?
  1. //某個ViewGroup類型的視圖  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.   //必須調用super.ononMeasure()或者直接調用setMeasuredDimension()方法設置該View大小,不然會報異常。  
  4.   super.onMeasure(widthMeasureSpec , heightMeasureSpec)  
  5.      //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  6.      //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  7.        
  8.   //遍歷每一個子View  
  9.   for(int i = 0 ; i < getChildCount() ; i++){  
  10.     View child = getChildAt(i);  
  11.     //調用子View的onMeasure,設置他們的大小。childWidthMeasureSpec , childHeightMeasureSpec ?  
  12.     child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  
  13.   }  
  14. }  


      Step 二、Step 3 代碼也比較好理解,但問題是咱們示例代碼中widthMeasureSpec、heightMeasureSpec是如何

 肯定的呢?父View是如何設定其值的?

  

      要想回答這個問題,咱們看是去源代碼裏找找答案吧。在ViewGroup.java類中,爲咱們提供了三個方法,去設置

個子View的大小,基本思想也如同咱們以前描述的思想:遍歷全部子View,設置每一個子View的大小。

     主要有以下方法:

[java]  view plain copy print ?
  1. /** 
  2.  * Ask all of the children of this view to measure themselves, taking into 
  3.  * account both the MeasureSpec requirements for this view and its padding. 
  4.  * We skip children that are in the GONE state The heavy lifting is done in 
  5.  * getChildMeasureSpec. 
  6.  */  
  7. //widthMeasureSpec 和  heightMeasureSpec 表示該父View的佈局要求  
  8. //遍歷每一個子View,而後調用measureChild()方法去實現每一個子View大小  
  9. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
  10.     final int size = mChildrenCount;  
  11.     final View[] children = mChildren;  
  12.     for (int i = 0; i < size; ++i) {  
  13.         final View child = children[i];  
  14.         if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不處於 「GONE」 狀態  
  15.             measureChild(child, widthMeasureSpec, heightMeasureSpec);  
  16.         }  
  17.     }  
  18. }  
  19.      
  20. /** 
  21.  * Ask one of the children of this view to measure itself, taking into 
  22.  * account both the MeasureSpec requirements for this view and its padding. 
  23.  * The heavy lifting is done in getChildMeasureSpec. 
  24.  * 
  25.  * @param child The child to measure 
  26.  * @param parentWidthMeasureSpec The width requirements for this view 
  27.  * @param parentHeightMeasureSpec The height requirements for this view 
  28.  */  
  29. //測量每一個子View高寬時,清楚了該View自己的邊距大小,即android:padding屬性 或android:paddingLeft等屬性標記  
  30. protected void measureChild(View child, int parentWidthMeasureSpec,  
  31.         int parentHeightMeasureSpec) {  
  32.     final LayoutParams lp = child.getLayoutParams(); // LayoutParams屬性  
  33.     //設置子View的childWidthMeasureSpec屬性,去除了該父View的邊距值  mPaddingLeft + mPaddingRight  
  34.     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  35.             mPaddingLeft + mPaddingRight, lp.width);  
  36.     //設置子View的childHeightMeasureSpec屬性,去除了該父View的邊距值  mPaddingTop + mPaddingBottom  
  37.     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
  38.             mPaddingTop + mPaddingBottom, lp.height);  
  39.   
  40.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  41. }  
  

     measureChildren()方法:遍歷全部子View,調用measureChild()方法去設置該子View的屬性值。

     measureChild()  方法   : 獲取特定子View的widthMeasureSpec、heightMeasureSpec,調用measure()方法

 設置子View的實際寬高值。

    getChildMeasureSpec()就是獲取子View的widthMeasureSpec、heightMeasureSpec值。

  

[java]  view plain copy print ?
  1. /** 
  2.  * Does the hard part of measureChildren: figuring out the MeasureSpec to 
  3.  * pass to a particular child. This method figures out the right MeasureSpec 
  4.  * for one dimension (height or width) of one child view. 
  5.  * 
  6.  * The goal is to combine information from our MeasureSpec with the 
  7.  * LayoutParams of the child to get the best possible results. 
  8.  */  
  9. // spec參數                                    表示該父View自己所佔的widthMeasureSpec 或  heightMeasureSpec值  
  10. // padding參數                          表示該父View的邊距大小,見於android:padding屬性 或android:paddingLeft等屬性標記  
  11. // childDimension參數  表示該子View內部LayoutParams屬性的值,能夠是wrap_content、match_parent、一個精確指(an exactly size),  
  12. //           例如:由android:width指定等。  
  13. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
  14.     int specMode = MeasureSpec.getMode(spec);  //得到父View的mode  
  15.     int specSize = MeasureSpec.getSize(spec);  //得到父View的實際值  
  16.   
  17.     int size = Math.max(0, specSize - padding); //父View爲子View設定的大小,減去邊距值,  
  18.   
  19.     int resultSize = 0;    //子View對應地 size 實際值 ,由下面的邏輯條件賦值  
  20.     int resultMode = 0;    //子View對應地 mode 值 , 由下面的邏輯條件賦值  
  21.   
  22.     switch (specMode) {  
  23.     // Parent has imposed an exact size on us  
  24.     //一、父View是EXACTLY的 !  
  25.     case MeasureSpec.EXACTLY:   
  26.         //1.一、子View的width或height是個精確值 (an exactly size)  
  27.         if (childDimension >= 0) {            
  28.             resultSize = childDimension;         //size爲精確值  
  29.             resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
  30.         }   
  31.         //1.二、子View的width或height爲 MATCH_PARENT/FILL_PARENT   
  32.         else if (childDimension == LayoutParams.MATCH_PARENT) {  
  33.             // Child wants to be our size. So be it.  
  34.             resultSize = size;                   //size爲父視圖大小  
  35.             resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
  36.         }   
  37.         //1.三、子View的width或height爲 WRAP_CONTENT  
  38.         else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  39.             // Child wants to determine its own size. It can't be  
  40.             // bigger than us.  
  41.             resultSize = size;                   //size爲父視圖大小  
  42.             resultMode = MeasureSpec.AT_MOST;    //mode爲AT_MOST 。  
  43.         }  
  44.         break;  
  45.   
  46.     // Parent has imposed a maximum size on us  
  47.     //二、父View是AT_MOST的 !      
  48.     case MeasureSpec.AT_MOST:  
  49.         //2.一、子View的width或height是個精確值 (an exactly size)  
  50.         if (childDimension >= 0) {  
  51.             // Child wants a specific size... so be it  
  52.             resultSize = childDimension;        //size爲精確值  
  53.             resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY 。  
  54.         }  
  55.         //2.二、子View的width或height爲 MATCH_PARENT/FILL_PARENT  
  56.         else if (childDimension == LayoutParams.MATCH_PARENT) {  
  57.             // Child wants to be our size, but our size is not fixed.  
  58.             // Constrain child to not be bigger than us.  
  59.             resultSize = size;                  //size爲父視圖大小  
  60.             resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
  61.         }  
  62.         //2.三、子View的width或height爲 WRAP_CONTENT  
  63.         else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  64.             // Child wants to determine its own size. It can't be  
  65.             // bigger than us.  
  66.             resultSize = size;                  //size爲父視圖大小  
  67.             resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
  68.         }  
  69.         break;  
  70.   
  71.     // Parent asked to see how big we want to be  
  72.     //三、父View是UNSPECIFIED的 !  
  73.     case MeasureSpec.UNSPECIFIED:  
  74.         //3.一、子View的width或height是個精確值 (an exactly size)  
  75.         if (childDimension >= 0) {  
  76.             // Child wants a specific size... let him have it  
  77.             resultSize = childDimension;        //size爲精確值  
  78.             resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY  
  79.         }  
  80.         //3.二、子View的width或height爲 MATCH_PARENT/FILL_PARENT  
  81.         else if (childDimension == LayoutParams.MATCH_PARENT) {  
  82.             // Child wants to be our size... find out how big it should  
  83.             // be  
  84.             resultSize = 0;                        //size爲0! ,其值未定  
  85.             resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
  86.         }   
  87.         //3.三、子View的width或height爲 WRAP_CONTENT  
  88.         else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  89.             // Child wants to determine its own size.... find out how  
  90.             // big it should be  
  91.             resultSize = 0;                        //size爲0! ,其值未定  
  92.             resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
  93.         }  
  94.         break;  
  95.     }  
  96.     //根據上面邏輯條件獲取的mode和size構建MeasureSpec對象。  
  97.     return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
  98. }  

       爲了便於分析,我將上面的邏輯判斷語句使用列表項進行了說明.


  getChildMeasureSpec()方法的主要功能以下:


        
根據父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View內部

  LayoutParams屬性值,共同決定子View的measureSpec值的大小。主要判斷條件主要爲MeasureSpec的mode

 類型以及LayoutParams的寬高實際值(lp.width,lp.height),見於以上所貼代碼中的列表項: 一、 1.1 ; 1.2 ; 1.3 ; 

  二、2.1等。


        例如,分析列表3:假設當父View爲MeasureSpec.UNSPECIFIED類型,即未定義時,只有當子View的width

 或height指定時,其mode才爲MeasureSpec.EXACTLY,否者該View size爲 0 ,mode爲MeasureSpec.UNSPECIFIED時

 ,即處於未指定狀態。

      由此能夠得出, 每一個View大小的設定都事由其父View以及該View共同決定的。但這只是一個指望的大小,每一個

 View在測量時最終大小的設定是由setMeasuredDimension()最終決定的。所以,最終肯定一個View的「測量長寬「是

 由如下幾個方面影響:

        一、父View的MeasureSpec屬性;

        二、子View的LayoutParams屬性 ;

        三、setMeasuredDimension()或者其它相似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

                setMeasuredDimension()原型:

[java]  view plain copy print ?
  1. //設置View在measure過程當中寬和高  
  2. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  3.     mMeasuredWidth = measuredWidth;  
  4.     mMeasuredHeight = measuredHeight;  
  5.   
  6.     mPrivateFlags |= MEASURED_DIMENSION_SET;  //設置了MEASURED_DIMENSION_SET標記  
  7. }  


  將上面列表項轉換爲表格爲:

                               

 


   這張表格更能幫助咱們分析View的MeasureSpec的肯定條件關係。


   爲了幫助你們理解,下面咱們分析某個窗口使用地xml佈局文件,咱們弄清楚該xml佈局文件中每一個View的

MeasureSpec值的組成。

    

[java]  view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/llayout"  
  4.        android:orientation="vertical"   
  5.     android:layout_width="match_parent"  
  6.        android:layout_height="match_parent">  
  7.       
  8.       
  9.     <TextView android:id="@+id/tv"   
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:text="@string/hello" />  
  13.   
  14. </LinearLayout>     

     該佈局文件共有兩個View:  ①、id爲llayout的LinearLayout佈局控件 ;

                                                   ②、id爲tv的TextView控件。


      假設LinearLayout的父View對應地widthSpec和heightSpec值皆爲MeasureSpec.EXACTLY類型(Activity窗口

  的父View爲DecorView,具體緣由見第三部分說明)。


       對LinearLayout而言比較簡單,因爲 android:layout_width="match_parent",所以其width對應地widthSpec 

  mode值爲MeasureSpec.EXACTLY , size由父視圖大小指定 ;  因爲android:layout_height = "match_parent",

  因此其height對應地heightSpec modeMeasureSpec.EXACTLY,size由父視圖大小指定 ;


       對TextView而言 ,其父View爲LinearLayout的widthSpec和heightSpec值皆爲MeasureSpec.EXACTLY類型,

 因爲android:layout_width="match_parent" , 所以其width對應地widthSpec mode值爲MeasureSpec.EXACTLY

 size由父視圖大小指定 ;  因爲android:layout_width="wrap_content" , 所以其height對應地widthSpec mode值爲

 MeasureSpec.AT_MOST,size由父視圖大小指定 。


    咱們繼續窺測下LinearLayout類是如何進行measure過程的:

[java]  view plain copy print ?
  1.  public class LinearLayout extends ViewGroup {  
  2. ...  
  3. @Override  //onMeasure方法。  
  4. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  5.     //判斷是垂直方向仍是水平方向,這兒咱們假設是VERTICAL垂直方向,  
  6.     if (mOrientation == VERTICAL) {  
  7.         measureVertical(widthMeasureSpec, heightMeasureSpec);  
  8.     } else {  
  9.         measureHorizontal(widthMeasureSpec, heightMeasureSpec);  
  10.     }  
  11. }  
  12. //垂直方向佈局  
  13.    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
  14.        mTotalLength = 0;         //該LinearLayout測量子View時的總高度。  
  15.     float totalWeight = 0;    //全部子View的權重和 , android:layout_weight  
  16.     int maxWidth = 0;         //保存子View中最大width值  
  17.        ...  
  18.        final int count = getVirtualChildCount();  //子View的個數  
  19.          
  20.        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  21.        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  22.           ...  
  23.        // See how tall everyone is. Also remember max width.  
  24.        for (int i = 0; i < count; ++i) {  
  25.            final View child = getVirtualChildAt(i);  
  26.               ...  
  27.            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
  28.   
  29.            totalWeight += lp.weight;    
  30.            //知足該條件地View會在該LinearLayout有剩餘高度時,才真正調用measure()  
  31.            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
  32.                ...  
  33.            } else {  
  34.                int oldHeight = Integer.MIN_VALUE;  
  35.                //若是View的hight值爲0,而且設置了android:layout_weight屬性,從新糾正其height值爲WRAP_CONTENT  
  36.                if (lp.height == 0 && lp.weight > 0) {  
  37.                    oldHeight = 0;  
  38.                    lp.height = LayoutParams.WRAP_CONTENT;  
  39.                }  
  40.                // Determine how big this child would like to be. If this or  
  41.                // previous children have given a weight, then we allow it to  
  42.                // use all available space (and we will shrink things later  
  43.                // if needed).  
  44.                //對每一個子View調用measure()方法  
  45.                measureChildBeforeLayout(  
  46.                       child, i, widthMeasureSpec, 0, heightMeasureSpec,  
  47.                       totalWeight == 0 ? mTotalLength : 0);  
  48.                  
  49.                //這三行代碼作了以下兩件事情:  
  50.                //一、得到該View的measuredHeight值,每一個View都會根據他們地屬性正確設置值  > 0 ;  
  51.                //二、更新mTotalLength值:取當前高度mTotalLength值與mTotalLength + childHeight 的最大值  
  52.                // 因而對於android:layout_height="wrap_height"屬性地LinearLayout控件也就知道了它的確切高度值了。  
  53.                final int childHeight = child.getMeasuredHeight();  
  54.                final int totalLength = mTotalLength;  
  55.                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +  
  56.                       lp.bottomMargin + getNextLocationOffset(child));  
  57.                ...  
  58.            }  
  59.            final int margin = lp.leftMargin + lp.rightMargin;  
  60.            final int measuredWidth = child.getMeasuredWidth() + margin;  
  61.            maxWidth = Math.max(maxWidth, measuredWidth);  
  62.            ...  
  63.        }  
  64.           //後續還有不少處理,包括繼續measure()某些符合條件地子View  
  65.        ...  
  66.    }  
  67.    void measureChildBeforeLayout(View child, int childIndex,  
  68.            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,  
  69.            int totalHeight) {  
  70.     //調用measureChildWithMargins()方法去設置子View大小  
  71.        measureChildWithMargins(child, widthMeasureSpec, totalWidth,  
  72.                heightMeasureSpec, totalHeight);  
  73.    }  
  74. ...  

          

        繼續看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內,基本流程同於measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理。

      measureChildWithMargins@ViewGroup.java 

[java]  view plain copy print ?
  1. /** 
  2.  * Ask one of the children of this view to measure itself, taking into 
  3.  * account both the MeasureSpec requirements for this view and its padding 
  4.  * and margins. The child must have MarginLayoutParams The heavy lifting is 
  5.  * done in getChildMeasureSpec. 
  6.  */  
  7. //基本流程同於measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理  
  8. //widthUsed參數  表示該父View已經使用的寬度  
  9. //heightUsed參數  表示該父View已經使用的高度  
  10. protected void measureChildWithMargins(View child,  
  11.         int parentWidthMeasureSpec, int widthUsed,  
  12.         int parentHeightMeasureSpec, int heightUsed) {  
  13.     final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  
  14.   
  15.     //得到子View的childWidthMeasureSpec和childHeightMeasureSpec值  
  16.     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  17.             mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin  
  18.                     + widthUsed, lp.width);  
  19.     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
  20.             mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  
  21.                     + heightUsed, lp.height);  
  22.   
  23.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  24. }  



    measure()過程時,LinearLayout類作了以下事情 :

            一、遍歷每一個子View,對其調用measure()方法;

            二、子View measure()完成後,須要取得該子View地寬高實際值,繼而作處理(例如:LinearLayout屬性爲

       android:widht="wrap_content"時,LinearLayout的實際width值則是每一個子View的width值的累加值)。

     

  2.2 WRAP_CONTENT、MATCH_PARENT以及measure動機揭祕


        子View地寬高實際值 ,即child.getMeasuredWidth()值得返回最終會是一個肯定值?  難道WRAP_CONTENT(

其值爲-2) 、MATCH_PARENT(值爲-1)或者說一個具體值(an exactly size > 0)。前面咱們說過,View最終「測量」值的

肯定是有三個部分組成地:

         ①、父View的MeasureSpec屬性;

         ②、子View的LayoutParams屬性 ;

         ③、setMeasuredDimension()或者其它相似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

   所以,一個View必須以某種合適地方法肯定它地最終大小。例如,以下自定義View:

[java]  view plain copy print ?
  1. //自定義View     
  2. public Class MyView extends View {  
  3.       
  4.      //針對不一樣地mode值,設置本View地大小  
  5.      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){  
  6.          //得到父View傳遞給咱們地測量需求  
  7.          int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  8.          int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  9.            
  10.          int width = 0 ;  
  11.          int height = 0 ;  
  12.          //對UNSPECIFIED 則拋出異常  
  13.          if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)  
  14.              throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED");  
  15.           
  16.          //精確指定  
  17.          if(widthMode == MeasureSpec.EXACTLY){  
  18.              width = 100 ;  
  19.          }  
  20.          //模糊指定  
  21.          else if(widthMode == MeasureSpec.AT_MOST )  
  22.              width = 50 ;   
  23.            
  24.           //精確指定  
  25.          if(heightMode == MeasureSpec.EXACTLY){  
  26.              height = 100 ;  
  27.          }  
  28.          //模糊指定  
  29.          else if(heightMode == MeasureSpec.AT_MOST )  
  30.              height = 50 ;  
  31.            
  32.          setMeasuredDimension(width , height) ;  
  33.      }  
  34. }  



          該自定義View重寫了onMeasure()方法,根據傳遞過來的widthMeasureSpec和heightMeasureSpec簡單設置了

 該View的mMeasuredWidth 和 mMeasuredHeight值。

      對於TextView而言,若是它地mode不是Exactly類型 , 它會根據一些屬性,例如:android:textStyle

  、android:textSizeandroid:typeface等去肯定TextView類地須要佔用地長和寬。

   

     所以,若是你地自定義View必須手動對不一樣mode作出處理。不然,則是mode對你而言是無效的。

   

      Android框架中提供地一系列View/ViewGroup都須要去進行這個measure()過程地 ,由於在layout()過程當中,父

  View須要調用getMeasuredWidth()或getMeasuredHeight()去爲每一個子View設置他們地佈局座標,只有肯定佈局

  座標後,才能真正地將該View 繪製(draw)出來,不然該View的layout大小爲0,得不到指望效果。咱們繼續看看

  LinearLayout的layout佈局過程:

[java]  view plain copy print ?
  1. public class LinearLayout extends ViewGroup {  
  2.     ...  
  3.     @Override  //layout 過程  
  4.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  5.         //假定是垂直方向佈局  
  6.         if (mOrientation == VERTICAL) {  
  7.             layoutVertical();  
  8.         } else {  
  9.             layoutHorizontal();  
  10.         }  
  11.     }  
  12.     //對每一個子View調用layout過程  
  13.     void layoutVertical() {  
  14.         ...  
  15.         final int count = getVirtualChildCount();  
  16.         ...  
  17.         for (int i = 0; i < count; i++) {  
  18.             final View child = getVirtualChildAt(i);  
  19.             if (child == null) {  //通常爲非null  
  20.                 childTop += measureNullChild(i);  
  21.             } else if (child.getVisibility() != GONE) {  
  22.                 //得到子View測量時的實際寬高值,  
  23.                 final int childWidth = child.getMeasuredWidth();  
  24.                 final int childHeight = child.getMeasuredHeight();  
  25.                   
  26.                 ...  
  27.                 //  封裝了child.layout()方法,見以下  
  28.                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),  
  29.                         childWidth, childHeight);   
  30.                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);  
  31.   
  32.                 i += getChildrenSkipCount(child, i);  
  33.             }  
  34.         }  
  35.     }  
  36.     //width = getMeasuredWidth() ; height = childHeight(); View的大小就是測量大小  
  37.     private void setChildFrame(View child, int left, int top, int width, int height) {  
  38.           
  39.         child.layout(left, top, left + width, top + height);  
  40.     }  
  41.     ...  
  42. }     

      對一個View進行measure操做地主要目的就是爲了肯定該View地佈局大小,見上面所示代碼。但measure操做

 一般是耗時的,所以對自定義ViewGroup而言,咱們能夠自由控制measure、layout過程,若是咱們知道如何layout

 一個View,咱們能夠跳過該ViewGroup地measure操做(onMeasure()方法中measure全部子View地),直接去layout


      在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明>>中,咱們自定義了一個     ViewGroup,  而且重寫了onMeasure()和onLayout()方法去分別操做每一個View。就該ViewGroup而言,咱們只須要

  重寫onLayout()操做便可,由於咱們知道如何layout每一個子View。以下代碼所示:


[java]  view plain copy print ?
  1. //自定義ViewGroup , 包含了三個LinearLayout控件,存放在不一樣的佈局位置  
  2. public class MultiViewGroup extends ViewGroup {  
  3.     private void init() {  
  4.         // 初始化3個 LinearLayout控件  
  5.         LinearLayout oneLL = new LinearLayout(mContext);  
  6.         oneLL.setBackgroundColor(Color.RED);  
  7.         addView(oneLL);  
  8.         ...  
  9.     }  
  10.     @Override  
  11.     // 咱們知曉每一個子View的layout佈局大小,所以咱們不須要爲每一個子View進行measure()操做了。  
  12. //  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  13. //      setMeasuredDimension(width, height);  
  14. //      // 設置該ViewGroup的大小  
  15. //      int width = MeasureSpec.getSize(widthMeasureSpec);  
  16. //      int height = MeasureSpec.getSize(heightMeasureSpec);  
  17. //      int childCount = getChildCount();  
  18. //      for (int i = 0; i < childCount; i++) {  
  19. //          View child = getChildAt(i);  
  20. //          // 設置每一個子視圖的大小 , 即全屏  
  21. //          child.measure(MultiScreenActivity.screenWidth, MultiScreenActivity.scrrenHeight);  
  22. //      }  
  23.     }  
  24.   
  25.     // layout過程  
  26.     @Override  
  27.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  28.         // TODO Auto-generated method stub  
  29.         Log.i(TAG, "--- start onLayout --");  
  30.         int startLeft = 0// 每一個子視圖的起始佈局座標  
  31.         int startTop = 10// 間距設置爲10px 至關於 android:marginTop= "10px"  
  32.         int childCount = getChildCount();  
  33.         Log.i(TAG, "--- onLayout childCount is -->" + childCount);  
  34.         for (int i = 0; i < childCount; i++) {  
  35.             View child = getChildAt(i);  
  36.             child.layout(startLeft, startTop,   
  37.                     startLeft + MultiScreenActivity.screenWidth,   
  38.                     startTop + MultiScreenActivity.scrrenHeight);  
  39.             startLeft = startLeft + MultiScreenActivity.screenWidth ; //校準每一個子View的起始佈局位置  
  40.             //三個子視圖的在屏幕中的分佈以下 [0 , 320] / [320,640] / [640,960]  
  41.         }  
  42.     }  
  43. }    

     更多關於自定義ViewGroup無須重寫measure動做的,能夠參考 Android API :

 

 三、root View被添加至窗口時,UI框架是如何設置其LayoutParams值


         老子道德經有言:「道生一,一輩子二,二生三,三生萬物。」  UI繪製也就是個遞歸過程。理解其基本架構後,
 也就「掌握了一箇中心點」了。在第一節中,咱們沒有說明開始UI繪製時 ,沒有說明mView.measure()參數地由來,
 參數也就是咱們本節須要弄懂的「道」 --- root View的 widthMeasureSpec和heightMeasureSpec 是如何肯定的。

   對於以下佈局文件: main.xml
[java]  view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"  
  6.     >  
  7. <TextView    
  8.     android:layout_width="fill_parent"   
  9.     android:layout_height="wrap_content"   
  10.     android:text="@string/hello"  
  11.     />  
  12. </LinearLayout>  
  
    當使用LayoutInflater類解析成View時 ,LinearLayout對象的LayoutParams參數爲null 。具體緣由請參考上篇博文

    任何一個View被添加至窗口時,都須要利用WindowManager類去操做。例如,以下代碼:
 
[java]  view plain copy print ?
  1. //顯示一個懸浮窗吧 , just so so   
  2. public void showView()  
  3. {  
  4.     //解析佈局文件  
  5.     LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  6.     //rootView對應地LayoutParams屬性值爲null,將會在UI繪製時設定其值  
  7.     View rootView = layoutInflater.inflate(R.layout.main, null);  
  8.       
  9.     WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);  
  10.     //設置WindowManager.LayoutParams參數值,做爲該窗口的各類屬性  
  11.     WindowManager.LayoutParams winparams = WindowManager.LayoutParams();  
  12.      // 以屏幕左上角爲原點,設置x、y初始值  
  13.     winparams.x = 0;  
  14.     winparams.y = 0;  
  15.   
  16.     //設置懸浮窗口長寬數據  
  17.     winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;;  
  18.     winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;;  
  19.        
  20.     windowManager.addView(rootView, winparams);  
  21. }  

[java]  view plain copy print ?
  1.   
       關於WindowManager的使用請看以下博客 :
                               <<android學習---- WindowManager 接口 >>
                              <<在Android中使用WindowManager實現懸浮窗口>>
      關於WindowManager.LayoutParams類說明請看以下博客: 
                              << android學習---- WindowManager.LayoutParams>>
       下面,咱們從得到WindowManager對象引用開始,一步步觀察addView()作了一些什麼事情。
   Step 1 、得到WindowManager對象服務 ,具體實現類在ContextImpl.java內中
           路徑: /frameworks/base/core/java/android/app/ContextImpl.java         
[java]  view plain copy print ?
  1. @Override  
  2. public Object getSystemService(String name) {  
  3.     if (WINDOW_SERVICE.equals(name)) {  
  4.         return WindowManagerImpl.getDefault();  
  5.     }  
  6.     ...  
  7. }  
         WindowManager是個接口,具體返回對象則是WindowManagerImpl的單例對象。

 Step 2 、 得到WindowManagerImpl的單例對象,以及部分源碼分析
           路徑: /frameworks/base/core/java/android/view/WindowManagerImpl.java 
[java]  view plain copy print ?
  1. public class WindowManagerImpl implements WindowManager{  
  2.          
  3.    public static WindowManagerImpl getDefault()  
  4.    {  
  5.        return mWindowManager;  
  6.    }  
  7.    //以特定Window屬性添加一個窗口  
  8.    public void addView(View view, ViewGroup.LayoutParams params)  
  9.    {  
  10.        addView(view, params, false);  
  11.    }  
  12.    //參數nest表示該窗口是否是一個字窗口  
  13.    private void addView(View view, ViewGroup.LayoutParams params, boolean nest)  
  14.    {   ...  
  15.        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;  
  16.          
  17.        ViewRoot root;  
  18.        View panelParentView = null;    //該子窗口對應地父窗口View  
  19.          
  20.        synchronized (this) {  
  21.             
  22.            ...//須要對傳遞過來地參數進行檢測...  
  23.              
  24.            //對每一個窗口皆構建一個ViewRoot對象  
  25.            root = new ViewRoot(view.getContext());  
  26.            root.mAddNesting = 1;  
  27.            //設置root View 的LayoutParams爲wparams,即WindowManager.LayoutParams類型  
  28.            view.setLayoutParams(wparams);  
  29.            ...//對參數檢測,以及拷貝原有數組...  
  30.              
  31.            //將窗口對應地view、root、wparams保存在屬性集合中  
  32.            mViews[index] = view;  
  33.            mRoots[index] = root;  
  34.            mParams[index] = wparams;  
  35.        }  
  36.        // do this last because it fires off messages to start doing things  
  37.        // 調用ViewRoot對象去通知系統添加一個窗口  
  38.        root.setView(view, wparams, panelParentView);  
  39.    }  
  40.    ...  
  41.    //這三個數組分別保存了一個窗口對應地屬性  
  42.    private View[] mViews;         //root View對象 , View類型  
  43.    private ViewRoot[] mRoots;     //ViewRoot類型 , 與WMS通訊  
  44.    private WindowManager.LayoutParams[] mParams;  //窗口屬性  
  45.      
  46.    //WindowManagerImpl實現了單例模式  
  47.    private static WindowManagerImpl mWindowManager = new WindowManagerImpl();  
  48. }  


       WindowManagerImpl類的三個數組集合保存了每一個窗口相關屬性,這樣咱們能夠經過這些屬性去操做特定的
 窗口(例如,能夠根據View去更新/銷燬該窗口)。當參數檢查成功時,構建一個ViewRoot對象,而且設置設置root
 View 的LayoutParams爲wparams,即WindowManager.LayoutParams類型。最後調用root.setView()方法去通知
 系統須要建立該窗口。咱們接下來往下看看ViewRoot類相關操做。
  
    Step 三、
   
[java]  view plain copy print ?
  1. public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {  
  2.          
  3.     View mView;   //全部窗口地root View     
  4.     final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();    
  5.   
  6.     ...  
  7.      /** 
  8.      * We have one child 
  9.      */  
  10.     public void setView(View view, WindowManager.LayoutParams attrs,  
  11.             View panelParentView) {  
  12.         synchronized (this) {  
  13.             if (mView == null) {  
  14.                 mView = view;  
  15.                 mWindowAttributes.copyFrom(attrs); //保存WindowManager.LayoutParams屬性值  
  16.                 attrs = mWindowAttributes;  
  17.                 ...  
  18.                   
  19.                 mAdded = true;  
  20.                 int res; /* = WindowManagerImpl.ADD_OKAY; */  
  21.   
  22.                 // Schedule the first layout -before- adding to the window  
  23.                 // manager, to make sure we do the relayout before receiving  
  24.                 // any other events from the system.  
  25.                 requestLayout();   //請求UI開始繪製。  
  26.                 mInputChannel = new InputChannel();  //建立一個InputChannel對象,接受消息  
  27.                 try {  
  28.                     //通知WindowManagerService添加一個窗口  
  29.                     res = sWindowSession.add(mWindow, mWindowAttributes,  
  30.                             getHostVisibility(), mAttachInfo.mContentInsets,  
  31.                             mInputChannel);  
  32.                 }   
  33.                 ...  
  34.                 view.assignParent(this);  //將root View的父View設置爲該ViewRoot對象(實現了ViewParent接口)  
  35.                 ...  
  36.             }  
  37.         }  
  38.     }  
  39. }  
            說明:ViewRoot類繼承了Handler,實現了ViewParent接口

  setView()方法地主要功能以下:
        一、保存相關屬性值,例如:mView、mWindowAttributes等;
        二、調用requestLayout()方法請求UI繪製,因爲ViewRoot是個Handler對象,異步請求;
        三、通知WindowManagerService添加一個窗口;
        四、註冊一個事件監聽管道,用來監聽:按鍵(KeyEvent)和觸摸(MotionEvent)事件。
  咱們這兒重點關注 requestLayout()方法請求UI繪製地流程。

  Step 四、異步調用請求UI繪製
   
[java]  view plain copy print ?
  1. /** 
  2.  * {@inheritDoc} 
  3.  */  
  4. public void requestLayout() {  
  5.     checkThread();        //檢查是否是UI線程調用,若是不是UI線程,會報異常  
  6.     mLayoutRequested = true;   //置爲真,表示須要進行measure和layout過程  
  7.     scheduleTraversals();    
  8. }  
  9. //開始UI繪製流程  
  10. public void scheduleTraversals() {  
  11.     if (!mTraversalScheduled) {  
  12.         mTraversalScheduled = true;       //防止屢次調用  
  13.         sendEmptyMessage(DO_TRAVERSAL);   //異步請求UI繪製  
  14.     }  
  15. }  
  16. @Override  
  17. public void handleMessage(Message msg) {  
  18.  switch (msg.what) {  
  19.         case DO_TRAVERSAL:  
  20.              performTraversals();  //開始UI繪製  
  21.              break;  
  22.  }  
  23. }  
   
          因爲performTraversals()方法比較複雜,咱們側重於第一次設置root View的widhtSpecSize以及    
  heightSpecSize值。
[java]  view plain copy print ?
  1. private void performTraversals() {  
  2.     // cache mView since it is used so much below...  
  3.     final View host = mView;  
  4.   
  5.     mTraversalScheduled = false;           
  6.     boolean surfaceChanged = false;  
  7.     WindowManager.LayoutParams lp = mWindowAttributes;    
  8.   
  9.     int desiredWindowWidth;              //表示該窗口指望width值  
  10.     int desiredWindowHeight;             //表示該窗口指望width值  
  11.     int childWidthMeasureSpec;           //保存root View的widthMeasureSpec  
  12.     int childHeightMeasureSpec;          //保存root View的heightMeasureSpec  
  13.   
  14.     final View.AttachInfo attachInfo = mAttachInfo;  
  15.   
  16.     final int viewVisibility = getHostVisibility();  
  17.     boolean viewVisibilityChanged = mViewVisibility != viewVisibility  
  18.             || mNewSurfaceNeeded;  
  19.   
  20.     float appScale = mAttachInfo.mApplicationScale;  
  21.   
  22.     WindowManager.LayoutParams params = null;  
  23.     if (mWindowAttributesChanged) {  
  24.         mWindowAttributesChanged = false;  
  25.         surfaceChanged = true;  
  26.         params = lp;  
  27.     }  
  28.     Rect frame = mWinFrame;  
  29.     if (mFirst) {   //mFirst表示是不是第一次繪製該Window  
  30.         fullRedrawNeeded = true;  
  31.         mLayoutRequested = true;  
  32.   
  33.         DisplayMetrics packageMetrics =  
  34.             mView.getContext().getResources().getDisplayMetrics();  
  35.         //第一次繪製時desiredWindowWidth,desiredWindowHeight 值大小爲屏幕大小  
  36.         desiredWindowWidth = packageMetrics.widthPixels;  
  37.         desiredWindowHeight = packageMetrics.heightPixels;  
  38.         ...  
  39.     } else {   //不是第一次繪製,則desiredWindowWidth值爲frame保存大小,frame值會由WMS填充  
  40.         desiredWindowWidth = frame.width();  
  41.         desiredWindowHeight = frame.height();  
  42.         ...  
  43.     }  
  44.     ...  
  45.     boolean insetsChanged = false;  
  46.   
  47.     if (mLayoutRequested) {  
  48.         ...//得到root View的widthMeasureSpec 和 heightMeasureSpec值  
  49.         childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
  50.         childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
  51.         //開始measure過程  
  52.         host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  53.     }  
  54.     ...  
  55.     final boolean didLayout = mLayoutRequested;  
  56.       
  57.     boolean triggerGlobalLayoutListener = didLayout  
  58.             || attachInfo.mRecomputeGlobalAttributes;  
  59.     if (didLayout) {  
  60.         ... //layout過程  
  61.        host.layout(00, host.mMeasuredWidth, host.mMeasuredHeight);  
  62.         ...  
  63.     }  
  64.     ...  
  65.     if (!cancelDraw && !newSurface) {  
  66.         mFullRedrawNeeded = false;  
  67.         draw(fullRedrawNeeded);  
  68.         ...  
  69. }  
[java]  view plain copy print ?
  1. /** 
  2.   * @param windowSize  The available width or height of the window 
  3.   * 
  4.   * @param rootDimension The layout params for one dimension (width or height) of the window. 
  5.  */  
  6.  private int getRootMeasureSpec(int windowSize, int rootDimension) {  
  7.      int measureSpec;  
  8.      switch (rootDimension) {  
  9.      case ViewGroup.LayoutParams.MATCH_PARENT:  
  10.          // Window can't resize. Force root view to be windowSize.  
  11.          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
  12.          break;  
  13.      case ViewGroup.LayoutParams.WRAP_CONTENT:  
  14.          // Window can resize. Set max size for root view.  
  15.          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
  16.          break;  
  17.      default:  
  18.          // Window wants to be an exact size. Force root view to be that size.  
  19.          measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
  20.          break;  
  21.      }  
  22.      return measureSpec;  
  23.  }         


      調用root View的measure()方法時,其參數是由getRootMeasureSpec()設置的,基本思路同咱們前面描述的
  差很少。貼出來的代碼只是簡簡單單列出了measure 、layout 、 draw 過程的調用點,裏面有不少邏輯處理,
  閱讀起來比較費勁,我也只能算是個囫圇吞棗水平。你們有興趣地能夠看看源碼,加深理解。

    

 
     


   



相關文章
相關標籤/搜索