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

Android View 

  繪製流程的三個步驟,即:

                      1、  measure過程 --- 測量過程

                      2、 layout 過程     --- 佈局過程
                      3、 draw 過程      --- 繪製過程


      要想對Android 中View這塊深入理解,對這三個步驟地學習是必不可少的 。

      今天,我着重講解下如下三個內容:

            1、 measure過程

            2、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT屬性的原理說明

            3、xml佈局文件解析成View樹的流程分析。


     希望對大家能有幫助。- -  分析版本基於Android 2.3



 1、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT 


       初入Android殿堂的同學們,對這三個屬性一定又愛又恨。愛的是使用起來挺爽地---照葫蘆畫瓢即可,恨的

  卻是時常混淆這幾個屬性地意義,需要三思而後行。在帶着大家重溫下這幾個屬性的用法吧(希望我沒有囉嗦)。


      這三個屬性都用來適應視圖的水平或垂直大小,一個以視圖的內容或尺寸爲基礎的佈局比精確地指定視圖範圍

  更加方便。

        ①  fill_parent

                設置一個視圖的佈局爲fill_parent將強制性地使視圖擴展至父元素大小。

        ② match_parent

               Android 中match_parent和fill_parent意思一樣,但match_parent更貼切,於是從2.2開始兩個詞都可以

          用,但2.3版本後建議使用match_parent。

       ③ wrap_content

              自適應大小,強制性地使視圖擴展以便顯示其全部內容。以TextView和ImageView控件爲例,設置爲

         wrap_content將完整顯示其內部的文本和圖像。佈局元素將根據內容更改大小。

       

      可不要重複造輪子,以上摘自<<Android fill_parent、wrap_content和match_parent的區別>>


      當然,我們可以設置View的確切寬高,而不是由以上屬性指定。

  1. android:layout_weight="wrap_content"   //自適應大小  
  2. android:layout_weight="match_parent"   //與父視圖等高  
  3. android:layout_weight="fill_parent"    //與父視圖等高  
  4. android:layout_weight="100dip"         //精確設置高度值爲 100dip  

      接下來,我們需要轉換下視角,看看ViewGroup.LayoutParams類及其派生類。


 2、ViewGroup.LayoutParams類及其派生類


    2.1、  ViewGroup.LayoutParams類說明

            Android API中如下介紹:

                LayoutParams are used by views to tell their parents how they want to be laid out.


     意思大概是說: View通過LayoutParams類告訴其父視圖它想要地大小(即,長度和寬度)。


    因此,每個View都包含一個ViewGroup.LayoutParams類或者其派生類,View類依賴於ViewGroup.LayoutParams。

        路徑:frameworks\base\core\java\android\view\View.java

  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
  2.   ...  
  3.   /** 
  4.    * The layout parameters associated with this view and used by the parent 
  5.    * {@link android.view.ViewGroup} to determine how this view should be 
  6.    * laid out. 
  7.    * {@hide} 
  8.    */  
  9.   //該View擁有的 LayoutParams屬性,父試圖添加該View時,會爲其賦值,特別注意,其類型爲ViewGroup.LayoutParams。  
  10.   protected ViewGroup.LayoutParams mLayoutParams;    
  11.   ...  
  12. }  

     2.2、  ViewGroup.LayoutParams源碼分析

      路徑位於:frameworks\base\core\java\android\view\ViewGroup.java

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ...  
  3.      public static class LayoutParams {  
  4.         /** 
  5.          * Special value for the height or width requested by a View. 
  6.          * FILL_PARENT means that the view wants to be as big as its parent, 
  7.          * minus the parent's padding, if any. This value is deprecated 
  8.          * starting in API Level 8 and replaced by {@link #MATCH_PARENT}. 
  9.          */  
  10.         @Deprecated  
  11.         public static final int FILL_PARENT = -1;  // 注意值爲-1,Android2.2版本不建議使用  
  12.         /** 
  13.          * Special value for the height or width requested by a View. 
  14.          * MATCH_PARENT means that the view wants to be as big as its parent, 
  15.          * minus the parent's padding, if any. Introduced in API Level 8. 
  16.          */  
  17.         public static final int MATCH_PARENT = -1// 注意值爲-1  
  18.         /** 
  19.          * Special value for the height or width requested by a View. 
  20.          * WRAP_CONTENT means that the view wants to be just large enough to fit 
  21.          * its own internal content, taking its own padding into account. 
  22.          */  
  23.         public static final int WRAP_CONTENT = -2// 注意值爲-2  
  24.         /** 
  25.          * Information about how wide the view wants to be. Can be one of the 
  26.          * constants FILL_PARENT (replaced by MATCH_PARENT , 
  27.          * in API Level 8) or WRAP_CONTENT. or an exact size. 
  28.          */  
  29.         public int width;  //該View的寬度,可以爲WRAP_CONTENT/MATCH_PARENT 或者一個具體值  
  30.         /** 
  31.          * Information about how tall the view wants to be. Can be one of the 
  32.          * constants FILL_PARENT (replaced by MATCH_PARENT , 
  33.          * in API Level 8) or WRAP_CONTENT. or an exact size. 
  34.          */  
  35.         public int height; //該View的高度,可以爲WRAP_CONTENT/MATCH_PARENT 或者一個具體值  
  36.         /** 
  37.          * Used to animate layouts. 
  38.          */  
  39.         public LayoutAnimationController.AnimationParameters layoutAnimationParameters;  
  40.         /** 
  41.          * Creates a new set of layout parameters. The values are extracted from 
  42.          * the supplied attributes set and context. The XML attributes mapped 
  43.          * to this set of layout parameters are:、 
  44.          */  
  45.         public LayoutParams(Context c, AttributeSet attrs) {  
  46.             TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);  
  47.             setBaseAttributes(a,  
  48.                     R.styleable.ViewGroup_Layout_layout_width,  
  49.                     R.styleable.ViewGroup_Layout_layout_height);  
  50.             a.recycle();  
  51.         }  
  52.   
  53.         /** 
  54.          * Creates a new set of layout parameters with the specified width 
  55.          * and height. 
  56.          */  
  57.         public LayoutParams(int width, int height) {  
  58.             this.width = width;  
  59.             this.height = height;  
  60.         }  
  61.         /** 
  62.          * Copy constructor. Clones the width and height values of the source. 
  63.          * 
  64.          * @param source The layout params to copy from. 
  65.          */  
  66.         public LayoutParams(LayoutParams source) {  
  67.             this.width = source.width;  
  68.             this.height = source.height;  
  69.         }  
  70.         /** 
  71.          * Used internally by MarginLayoutParams. 
  72.          * @hide 
  73.          */  
  74.         LayoutParams() {  
  75.         }  
  76.         /** 
  77.          * Extracts the layout parameters from the supplied attributes. 
  78.          * 
  79.          * @param a the style attributes to extract the parameters from 
  80.          * @param widthAttr the identifier of the width attribute 
  81.          * @param heightAttr the identifier of the height attribute 
  82.          */  
  83.         protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {  
  84.             width = a.getLayoutDimension(widthAttr, "layout_width");  
  85.             height = a.getLayoutDimension(heightAttr, "layout_height");  
  86.         }  
  87. }  

       我們發現FILL_PARENT/MATCH_PARENT值爲 -1 ,WRAP_CONETENT值爲-2,是不是有點詫異? 將值

  設置爲負值的目的是爲了區別View的具體值(an exact size) 總是大於0的。


       ViewGroup子類可以實現自定義LayoutParams,自定義LayoutParams提供了更好地擴展性,例如LinearLayout

 就有LinearLayout. LayoutParams自定義類(見下文)。整個LayoutParams類家族還是挺複雜的。

      ViewGroup.LayoutParams及其常用派生類的類圖(部分類圖)如下:

                  


                              該類圖是在太龐大了,大家有興趣的去看看Android API吧。

           


      前面我們說過,每個View都包含一個ViewGroup.LayoutParams類或者其派生類,下面我們的疑問是Android框架

 中時如何爲View設置其LayoutParams屬性的。


     有兩種方法會設置View的LayoutParams屬性:

       1、 直接添加子View時,常見於如下幾種方法:ViewGroup.java

  1. //Adds a child view.      
  2. void addView(View child, int index)  
  3. //Adds a child view with this ViewGroup's default layout parameters   
  4. //and the specified width and height.  
  5. void addView(View child, int width, int height)  
  6. //Adds a child view with the specified layout parameters.         
  7. void addView(View child, ViewGroup.LayoutParams params)  

         三個重載方法的區別只是添加View時構造LayoutParams對象的方式不同而已,稍後我們探尋一下它們的源碼。

      2、 通過xml佈局文件指定某個View的屬性爲:android:layout_heigth=””以及android:layout_weight=”” 時。

    總的來說,這兩種方式都會設定View的LayoutParams屬性值----指定的或者Default值。

  方式1流程分析

     直接添加子View時,比較容易理解,我們先來看看這種方式設置LayoutParams的過程:

         路徑:\frameworks\base\core\java\android\view\ViewGroup.java

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ...  
  3.     /** 
  4.      * Adds a child view. If no layout parameters are already set on the child, the 
  5.      * default parameters for this ViewGroup are set on the child. 
  6.      * 
  7.      * @param child the child view to add 
  8.      * 
  9.      * @see #generateDefaultLayoutParams() 
  10.      */  
  11.     public void addView(View child) {  
  12.         addView(child, -1);  
  13.     }  
  14.     /** 
  15.      * Adds a child view. If no layout parameters are already set on the child, the 
  16.      * default parameters for this ViewGroup are set on the child. 
  17.      * 
  18.      * @param child the child view to add 
  19.      * @param index the position at which to add the child 
  20.      * 
  21.      * @see #generateDefaultLayoutParams() 
  22.      */  
  23.     public void addView(View child, int index) {  
  24.         LayoutParams params = child.getLayoutParams();  
  25.         if (params == null) {  
  26.             params = generateDefaultLayoutParams(); //返回默認地LayoutParams類,作爲該View的屬性值  
  27.             if (params == null) {//如果不能獲取到LayoutParams對象,則拋出異常。  
  28.                 throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");  
  29.             }  
  30.         }  
  31.         addView(child, index, params);  
  32.     }  
  33.     /** 
  34.      * Adds a child view with this ViewGroup's default layout parameters and the 
  35.      * specified width and height. 
  36.      * 
  37.      * @param child the child view to add 
  38.      */  
  39.     public void addView(View child, int width, int height) {  
  40.         //返回默認地LayoutParams類,作爲該View的屬性值  
  41.         final LayoutParams params = generateDefaultLayoutParams();   
  42.         params.width = width;   //重新設置width值  
  43.         params.height = height; //重新設置height值  
  44.         addView(child, -1, params); //這兒,我們有指定width、height的大小了。  
  45.     }  
  46.     /** 
  47.      * Adds a child view with the specified layout parameters. 
  48.      * 
  49.      * @param child the child view to add 
  50.      * @param params the layout parameters to set on the child 
  51.      */  
  52.     public void addView(View child, LayoutParams params) {  
  53.         addView(child, -1, params);  
  54.     }  
  55.     /** 
  56.      * Adds a child view with the specified layout parameters. 
  57.      * 
  58.      * @param child the child view to add 
  59.      * @param index the position at which to add the child 
  60.      * @param params the layout parameters to set on the child 
  61.      */  
  62.     public void addView(View child, int index, LayoutParams params) {  
  63.         ...  
  64.         // addViewInner() will call child.requestLayout() when setting the new LayoutParams  
  65.         // therefore, we call requestLayout() on ourselves before, so that the child's request  
  66.         // will be blocked at our level  
  67.         requestLayout();  
  68.         invalidate();  
  69.         addViewInner(child, index, params, false);  
  70.     }  
  71.     /** 
  72.      * Returns a set of default layout parameters. These parameters are requested 
  73.      * when the View passed to {@link #addView(View)} has no layout parameters 
  74.      * already set. If null is returned, an exception is thrown from addView. 
  75.      * 
  76.      * @return a set of default layout parameters or null 
  77.      */  
  78.     protected LayoutParams generateDefaultLayoutParams() {  
  79.         //width 爲 WRAP_CONTENT大小 , height 爲WRAP_CONTENT   
  80.         //ViewGroup的子類可以重寫該方法,達到其特定要求。稍後會以LinearLayout類爲例說明。  
  81.         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);  
  82.     }  
  83.     private void addViewInner(View child, int index, LayoutParams params,  
  84.             boolean preventRequestLayout) {  
  85.   
  86.         if (!checkLayoutParams(params)) { //params對象是否爲null  
  87.             params = generateLayoutParams(params); //如果params對象是爲null,重新構造個LayoutParams對象  
  88.         }  
  89.         //preventRequestLayout值爲false  
  90.         if (preventRequestLayout) {    
  91.             child.mLayoutParams = params; //爲View的mLayoutParams屬性賦值  
  92.         } else {  
  93.             child.setLayoutParams(params);//爲View的mLayoutParams屬性賦值,但會調用requestLayout()請求重新佈局  
  94.         }  
  95.         //if else 語句會設置View爲mLayoutParams屬性賦值  
  96.         ...  
  97.     }  
  98.     ...  
  99. }  

      主要功能就是在添加子View時爲其構建了一個LayoutParams對象。但更重要的是,ViewGroup的子類可以重載

 上面的幾個方法,返回特定的LayoutParams對象,例如:對於LinearLayout而言,則是LinearLayout.LayoutParams

 對象。這麼做地目的是,能在其他需要它的地方,可以將其強制轉換成LinearLayout.LayoutParams對象。


      LinearLayout重寫函數地實現爲:

  1. public class LinearLayout extends ViewGroup {  
  2.     ...  
  3.     @Override  
  4.     public LayoutParams generateLayoutParams(AttributeSet attrs) {  
  5.         return new LinearLayout.LayoutParams(getContext(), attrs);  
  6.     }  
  7.     @Override  
  8.     protected LayoutParams generateDefaultLayoutParams() {  
  9.         //該LinearLayout是水平方向還是垂直方向  
  10.         if (mOrientation == HORIZONTAL) {   
  11.             return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);  
  12.         } else if (mOrientation == VERTICAL) {  
  13.             return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);  
  14.         }  
  15.         return null;  
  16.     }  
  17.     @Override  
  18.     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {  
  19.         return new LayoutParams(p);  
  20.     }  
  21.     /** 
  22.      * Per-child layout information associated with ViewLinearLayout. 
  23.      *  
  24.      * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight 
  25.      * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity 
  26.      */ //自定義的LayoutParams類  
  27.     public static class LayoutParams extends ViewGroup.MarginLayoutParams {  
  28.         /** 
  29.          * Indicates how much of the extra space in the LinearLayout will be 
  30.          * allocated to the view associated with these LayoutParams. Specify 
  31.          * 0 if the view should not be stretched. Otherwise the extra pixels 
  32.          * will be pro-rated among all views whose weight is greater than 0. 
  33.          */  
  34.         @ViewDebug.ExportedProperty(category = "layout")  
  35.         public float weight;      //  見於屬性,android:layout_weight=""  ;  
  36.         /** 
  37.          * Gravity for the view associated with these LayoutParams. 
  38.          * 
  39.          * @see android.view.Gravity 
  40.          */  
  41.         public int gravity = -1;  // 見於屬性, android:layout_gravity=""  ;   
  42.         /** 
  43.          * {@inheritDoc} 
  44.          */  
  45.         public LayoutParams(Context c, AttributeSet attrs) {  
  46.             super(c, attrs);  
  47.             TypedArray a =c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);  
  48.             weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);  
  49.             gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);  
  50.   
  51.             a.recycle();  
  52.         }  
  53.         /** 
  54.          * {@inheritDoc} 
  55.          */  
  56.         public LayoutParams(int width, int height) {  
  57.             super(width, height);  
  58.             weight = 0;  
  59.         }  
  60.         /** 
  61.          * Creates a new set of layout parameters with the specified width, height 
  62.          * and weight. 
  63.          * 
  64.          * @param width the width, either {@link #MATCH_PARENT}, 
  65.          *        {@link #WRAP_CONTENT} or a fixed size in pixels 
  66.          * @param height the height, either {@link #MATCH_PARENT}, 
  67.          *        {@link #WRAP_CONTENT} or a fixed size in pixels 
  68.          * @param weight the weight 
  69.          */  
  70.         public LayoutParams(int width, int height, float weight) {  
  71.             super(width, height);  
  72.             this.weight = weight;  
  73.         }  
  74.         public LayoutParams(ViewGroup.LayoutParams p) {  
  75.             super(p);  
  76.         }  
  77.         public LayoutParams(MarginLayoutParams source) {  
  78.             super(source);  
  79.         }  
  80.     }  
  81.     ...  
  82. }  

       LinearLayout.LayoutParams類繼承至ViewGroup.MarginLayoutParams類,添加了對android:layout_weight以及

   android:layout_gravity這兩個屬性的獲取和保存。而且它的重寫函數返回的都是LinearLayout.LayoutParams

   類型。樣,我們可以再對子View進行其他操作時,可以將將其強制轉換成LinearLayout.LayoutParams對象進行

   使用。

         例如,LinearLayout進行measure過程,使用了LinearLayout.LayoutParam對象,有如下代碼:

  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.      * Measures the children when the orientation of this LinearLayout is set 
  14.      * to {@link #VERTICAL}. 
  15.      * 
  16.      * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. 
  17.      * @param heightMeasureSpec Vertical space requirements as imposed by the parent. 
  18.      * 
  19.      * @see #getOrientation() 
  20.      * @see #setOrientation(int) 
  21.      * @see #onMeasure(int, int) 
  22.      */  
  23.       void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
  24.             mTotalLength = 0;  
  25.             ...  
  26.             // See how tall everyone is. Also remember max width.  
  27.             for (int i = 0; i < count; ++i) {  
  28.                 final View child = getVirtualChildAt(i); //獲得索引處爲i的子VIew     
  29.                 ...  
  30.                 //注意,我們將類型爲 ViewGroup.LayoutParams的實例對象強制轉換爲了LinearLayout.LayoutParams,  
  31.                 //即父對象轉換爲了子對象,能這樣做的原因就是LinearLayout的所有子View的LayoutParams類型都爲  
  32.                 //LinearLayout.LayoutParams  
  33.                 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
  34.                 ...  
  35.         }  
  36.     ...  
  37. }  


        超類ViewGroup.LayoutParams強制轉換爲了子類LinearLayout.LayoutParams,因爲LinearLayout的每個

  ”直接“子ViewLayoutParams屬性都是LinearLayout.LayoutParams類型,因此可以安全轉換。


       PS : Android 2.3源碼Launcher2中也實現了自定義的LayoutParams類,在IDLE界面的每個View至少包含如下

  信息:所在X方向的單元格索引和高度、所在Y方向的單元格索引和高度等。

            路徑: packages\apps\Launcher2\src\com\android\launcher2\CellLayout.java

  1. public class CellLayout extends ViewGroup {  
  2.     ...   
  3.     public static class LayoutParams extends ViewGroup.MarginLayoutParams {  
  4.             /** 
  5.              * Horizontal location of the item in the grid. 
  6.              */  
  7.             public int cellX;   //X方向的單元格索引  
  8.             /** 
  9.              * Vertical location of the item in the grid. 
  10.              */  
  11.             public int cellY;   //Y方向的單元格索引  
  12.             /** 
  13.              * Number of cells spanned horizontally by the item. 
  14.              */  
  15.             public int cellHSpan;  //水平方向所佔高度  
  16.             /** 
  17.              * Number of cells spanned vertically by the item. 
  18.              */  
  19.             public int cellVSpan;  //垂直方向所佔高度  
  20.             ...  
  21.             public LayoutParams(Context c, AttributeSet attrs) {  
  22.                 super(c, attrs);  
  23.                 cellHSpan = 1;  //默認爲高度 1  
  24.                 cellVSpan = 1;  
  25.             }  
  26.   
  27.             public LayoutParams(ViewGroup.LayoutParams source) {  
  28.                 super(source); //默認爲高度 1  
  29.                 cellHSpan = 1;  
  30.                 cellVSpan = 1;  
  31.             }  
  32.               
  33.             public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {  
  34.                 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);  
  35.                 this.cellX = cellX;  
  36.                 this.cellY = cellY;  
  37.                 this.cellHSpan = cellHSpan;  
  38.                 this.cellVSpan = cellVSpan;  
  39.             }  
  40.             ...  
  41.         }  
  42.     ...  
  43. }  

     對該自定義CellLayout.LayoutParams類的使用可以參考LinearLayout.LayoutParams類,我也不再贅述了。


 方法2流程分析

        使用屬性android:layout_heigth=””以及android:layout_weight=”” 時,爲某個View設置LayoutParams值。

 

       其實這種賦值方法其實也如同前面那種,只不過它需要一個前期孵化過程---需要利用XML解析將佈局文件

  解析成一個完整的View樹,可別小看它了,所有Xxx.xml的佈局文件都需要解析成一個完整的View樹。下面,

  我們就來仔細走這個過程,重點關注如下兩個方面

         ①、xml佈局是如何解析成View樹的 ;

         ②、android:layout_heigth=””和android:layout_weight=””的解析。


        PS: 一直以來,我都想當然android:layout_heigth以及android:layout_weight這兩個屬性的解析過程是在

   View.java內部完成的,但當我真正去找尋時,卻一直沒有在View.java類或者ViewGroup.java類找到。直到一位

   網友的一次提問,才發現它們的藏身之地。


3、佈局文件解析流程分析


       解析佈局文件時,使用的類爲LayoutInflater。 關於該類的使用請參考如下博客:

                            <android中LayoutInflater的使用 >>

      主要有如下API方法:

        public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)

          public View inflate (int resource, ViewGroup root)

          public View inflate (int resource, ViewGroup root, boolean attachToRoot)

     這三個類主要迷惑之處在於地三個參數attachToRoot,即是否將該View樹添加到root中去。具體可看這篇博客:

                                       <<關於inflate的第3個參數>>

    當然還有LayoutInflater的inflate()的其他重載方法,大家可以自行了解下。

     

    我利用下面的例子給大家走走這個流程 :

  1. public class MainActivity extends Activity {  
  2.     /** Called when the activity is first created. */  
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         //1、該方法最終也會調用到 LayoutInflater的inflate()方法中去解析。  
  7.         setContentView(R.layout.main);  
  8.           
  9.         //2、使用常見的API方法去解析xml佈局文件,  
  10.         LayoutInflater layoutInflater = (LayoutInflater)getSystemService();  
  11.         View root = layoutInflater.inflate(R.layout.main, null);  
  12.     }  
  13. }  

   Step 1、獲得LayoutInflater的引用。

         路徑:\frameworks\base\core\java\android\app\ContextImpl.java

  1. /** 
  2.  * Common implementation of Context API, which provides the base 
  3.  * context object for Activity and other application components. 
  4.  */  
  5. class ContextImpl extends Context {  
  6.     if (WINDOW_SERVICE.equals(name)) {  
  7.         return WindowManagerImpl.getDefault();  
  8.     } else if (LAYOUT_INFLATER_SERVICE.equals(name)) {  
  9.         synchronized (mSync) {  
  10.             LayoutInflater inflater = mLayoutInflater;  
  11.             //是否已經賦值,如果是,直接返回引用  
  12.             if (inflater != null) {  
  13.                 return inflater;  
  14.             }  
  15.             //返回一個LayoutInflater對象,getOuterContext()指的是我們的Activity、Service或者Application引用  
  16.             mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext());  
  17.             return inflater;  
  18.         }  
  19.     } else if (ACTIVITY_SERVICE.equals(name)) {  
  20.         return getActivityManager();  
  21.     }...  
  22. }  

         繼續去PolicyManager查詢對應函數,看看內部實現。    

           徑:frameworks\base\core\java\com\android\internal\policy\PolicyManager.java

  1. public final class PolicyManager {  
  2.     private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";  
  3.     private static final IPolicy sPolicy;   // 這可不是Binder機制額,這只是是一個接口,別想多啦  
  4.     static {  
  5.         // Pull in the actual implementation of the policy at run-time  
  6.         try {  
  7.             Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);  
  8.             sPolicy = (IPolicy)policyClass.newInstance();  
  9.         }  
  10.         ...  
  11.     }  
  12.     ...  
  13.     public static LayoutInflater makeNewLayoutInflater(Context context) {  
  14.         return sPolicy.makeNewLayoutInflater(context); //繼續去實現類中去查找  
  15.     }  
  16. }  
    IPolicy接口的實現對爲Policy類。路徑:/frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java

  1. //Simple implementation of the policy interface that spawns the right  
  2. //set of objects  
  3. public class Policy implements IPolicy{  
  4.     ...  
  5.     public PhoneLayoutInflater makeNewLayoutInflater(Context context) {  
  6.         //實際上返回的是PhoneLayoutInflater類。  
  7.         return new PhoneLayoutInflater(context);  
  8.     }  
  9. }  
  10. //PhoneLayoutInflater繼承至LayoutInflater類  
  11. public class PhoneLayoutInflater extends LayoutInflater {  
  12.     ...  
  13.     /** 
  14.      * Instead of instantiating directly, you should retrieve an instance 
  15.      * through {@link Context#getSystemService} 
  16.      *  
  17.      * @param context The Context in which in which to find resources and other 
  18.      *                application-specific things. 
  19.      *  
  20.      * @see Context#getSystemService 
  21.      */  
  22.     public PhoneLayoutInflater(Context context) {  
  23.         super(context);  
  24.     }  
  25.     ...  
  26. }  


       LayoutInflater是個抽象類,實際上我們返回的是PhoneLayoutInflater類,但解析過程的操作基本上是在

  LayoutInflater中完成地。



   Step 2、調用inflate()方法去解析佈局文件。
  1. public abstract class LayoutInflater {  
  2.     ...  
  3.     public View inflate(int resource, ViewGroup root) {  
  4.         //繼續看下個函數,注意root爲null  
  5.         return inflate(resource, root, root != null);   
  6.     }  
  7.       
  8.     public View inflate(int resource, ViewGroup root, boolean attachToRoot) {  
  9.         //獲取一個XmlResourceParser來解析XML文件---佈局文件。  
  10.         //XmlResourceParser類以及xml是如何解析的,大家自己有興趣找找。  
  11.         XmlResourceParser parser = getContext().getResources().getLayout(resource);  
  12.         try {  
  13.             return inflate(parser, root, attachToRoot);  
  14.         } finally {  
  15.             parser.close();  
  16.         }  
  17.     }  
  18. }  
  19. /** 
  20.  * The XML parsing interface returned for an XML resource.  This is a standard 
  21.  * XmlPullParser interface, as well as an extended AttributeSet interface and 
  22.  * an additional close() method on this interface for the client to indicate 
  23.  * when it is done reading the resource. 
  24.  */  
  25. public interface XmlResourceParser extends XmlPullParser, AttributeSet {  
  26.     /** 
  27.      * Close this interface to the resource.  Calls on the interface are no 
  28.      * longer value after this call. 
  29.      */  
  30.     public void close();  
  31. }  


      我們獲得了一個當前應用程序環境的XmlResourceParser對象,該對象的主要作用就是來解析xml佈局文件的。

  XmlResourceParser類是個接口類,更多關於XML解析的,大家可以參考下面博客:

                              <<android之XmlResourceParser類使用實例>>

                                    <<android解析xml文件的方式(其一)>>

                                    <<android解析xml文件的方式(其二)>>

                                    <<android解析xml文件的方式(其三)>>



   Step 3 、真正地開始解析工作 。
  1. public abstract class LayoutInflater {  
  2.     ...  
  3.     /** 
  4.      * Inflate a new view hierarchy from the specified XML node. Throws 
  5.      * {@link InflateException} if there is an error. 
  6.      */  
  7.     //我們傳遞過來的參數如下: root 爲null , attachToRoot爲false 。  
  8.     public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
  9.         synchronized (mConstructorArgs) {  
  10.             final AttributeSet attrs = Xml.asAttributeSet(parser);  
  11.             Context lastContext = (Context)mConstructorArgs[0];  
  12.             mConstructorArgs[0] = mContext;  //該mConstructorArgs屬性最後會作爲參數傳遞給View的構造函數  
  13.             View result = root;  //根View  
  14.   
  15.             try {  
  16.                 // Look for the root node.  
  17.                 int type;  
  18.                 while ((type = parser.next()) != XmlPullParser.START_TAG &&  
  19.                         type != XmlPullParser.END_DOCUMENT) {  
  20.                     // Empty  
  21.                 }  
  22.                 ...  
  23.                 final String name = parser.getName();  //節點名,即API中的控件或者自定義View完整限定名。  
  24.                 if (TAG_MERGE.equals(name)) { // 處理<merge />標籤  
  25.                     if (root == null || !attachToRoot) {  
  26.                         throw new InflateException("<merge /> can be used only with a valid "  
  27.                                 + "ViewGroup root and attachToRoot=true");  
  28.                     }  
  29.                     //將<merge />標籤的View樹添加至root中,該函數稍後講到。  
  30.                     rInflate(parser, root, attrs);  
  31.                 } else {  
  32.                     // Temp is the root view that was found in the xml  
  33.                     //創建該xml佈局文件所對應的根View。  
  34.                     View temp = createViewFromTag(name, attrs);   
  35.   
  36.                     ViewGroup.LayoutParams params = null;  
  37.   
  38.                     if (root != null) {  
  39.                         // Create layout params that match root, if supplied  
  40.                         //根據AttributeSet屬性獲得一個LayoutParams實例,記住調用者爲root。  
  41.                         params = root.generateLayoutParams(attrs);   
  42.                         if (!attachToRoot) { //重新設置temp的LayoutParams  
  43.                             // Set the layout params for temp if we are not  
  44.                             // attaching. (If we are, we use addView, below)  
  45.                             temp.setLayoutParams(params);  
  46.                         }  
  47.                     }  
  48.                     // Inflate all children under temp  
  49.                     //添加所有其子節點,即添加所有字View  
  50.                     rInflate(parser, temp, attrs);  
  51.                       
  52.                     // We are supposed to attach all the views we found (int temp)  
  53.                     // to root. Do that now.  
  54.                     if (root != null && attachToRoot) {  
  55.                         root.addView(temp, params);  
  56.                     }  
  57.                     // Decide whether to return the root that was passed in or the  
  58.                     // top view found in xml.  
  59.                     if (root == null || !attachToRoot) {  
  60.                         result = temp;  
  61.                     }  
  62.                 }  
  63.             }   
  64.             ...  
  65.             return result;  
  66.         }  
  67.     }  
  68.       
  69.     /* 
  70.      * default visibility so the BridgeInflater can override it. 
  71.      */  
  72.     View createViewFromTag(String name, AttributeSet attrs) {  
  73.         //節點是否爲View,如果是將其重新賦值,形如 <View class="com.qin.xxxView"></View>  
  74.         if (name.equals("view")) {    
  75.             name = attrs.getAttributeValue(null"class");  
  76.         }  
  77.         try {  
  78.             View view = (mFactory == null) ? null : mFactory.onCreateView(name,  
  79.                     mContext, attrs);  //沒有設置工廠方法  
  80.   
  81.             if (view == null) {  
  82.                 //通過這個判斷是Android API的View,還是自定義View  
  83.                 if (-1 == name.indexOf('.')) {  
  84.                     view = onCreateView(name, attrs); //創建Android API的View實例  
  85.                 } else {  
  86.                     view = createView(name, null, attrs);//創建一個自定義View實例  
  87.                 }  
  88.             }  
  89.             return view;  
  90.         }   
  91.         ...  
  92.     }  
  93.     //獲得具體視圖的實例對象  
  94.     public final View createView(String name, String prefix, AttributeSet attrs) {  
  95.         Constructor constructor = sConstructorMap.get(name);  
  96.         Class clazz = null;  
  97.         //以下功能主要是獲取如下三個類對象:  
  98.         //1、類加載器  ClassLoader  
  99.         //2、Class對象  
  100.         //3、類的構造方法句柄 Constructor  
  101.         try {  
  102.             if (constructor == null) {  
  103.             // Class not found in the cache, see if it's real, and try to add it  
  104.             clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name);  
  105.             ...  
  106.             constructor = clazz.getConstructor(mConstructorSignature);  
  107.             sConstructorMap.put(name, constructor);  
  108.         } else {  
  109.             // If we have a filter, apply it to cached constructor  
  110.             if (mFilter != null) {  
  111.                 ...     
  112.             }  
  113.         }  
  114.             //傳遞參數獲得該View實例對象  
  115.             Object[] args = mConstructorArgs;  
  116.             args[1] = attrs;  
  117.             return (View) constructor.newInstance(args);  
  118.         }   
  119.         ...  
  120.     }  
  121.   
  122. }  

     這段代碼的作用是獲取xml佈局文件的root View,做了如下兩件事情

          1、獲取xml佈局的View實例,通過createViewFromTag()方法獲取,該方法會判斷節點名是API 控件

            還是自定義控件,繼而調用合適的方法去實例化View。

          2、判斷root以及attachToRoot參數,重新設置root View值以及temp變量的LayoutParams值。


        如果仔細看着段代碼,不知大家心裏有沒有疑惑:當root爲null時,我們的temp變量的LayoutParams值是爲

  null的,即它不會被賦值?有個View的LayoutParams值爲空,那麼,在系統中不會報異常嗎?見下面部分

  代碼:

  1. //我們傳遞過來的參數如下: root 爲null , attachToRoot爲false 。  
  2. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
  3.     synchronized (mConstructorArgs) {  
  4.         ...  
  5.         try {  
  6.               
  7.             ...  
  8.             if (TAG_MERGE.equals(name)) { // 處理<merge />標籤  
  9.                 ...  
  10.             } else {  
  11.                 // Temp is the root view that was found in the xml  
  12.                 //創建該xml佈局文件所對應的根View。  
  13.                 View temp = createViewFromTag(name, attrs);   
  14.                 ViewGroup.LayoutParams params = null;  
  15.   
  16.                 //注意!!! root爲null時,temp變量的LayoutParams屬性不會被賦值的。  
  17.                 if (root != null) {  
  18.                     // Create layout params that match root, if supplied  
  19.                     //根據AttributeSet屬性獲得一個LayoutParams實例,記住調用者爲root。  
  20.                     params = root.generateLayoutParams(attrs);   
  21.                     if (!attachToRoot) { //重新設置temp的LayoutParams  
  22.                         // Set the layout params for temp if we are not  
  23.                         // attaching. (If we are, we use addView, below)  
  24.                         temp.setLayoutParams(params);  
  25.                     }  
  26.                 }  
  27.                 ...  
  28.             }  
  29.         }   
  30.         ...  
  31.     }  
  32. }  

        關於這個問題的詳細答案,我會在後面講到。這兒我簡單說下,任何View樹的頂層View被添加至窗口時,

  一般調用WindowManager.addView()添加至窗口時,在這個方法中去做進一步處理。即使LayoutParams

  值空,UI框架每次measure()時都忽略該View的LayoutParams值,而是直接傳遞MeasureSpec值至View樹。


       接下來,我們關注另外一個函數,rInflate(),該方法會遞歸調用每個View下的子節點,以當前View作爲根View

 形成一個View樹。

  1. /** 
  2.  * Recursive method used to descend down the xml hierarchy and instantiate 
  3.  * views, instantiate their children, and then call onFinishInflate(). 
  4.  */  
  5. //遞歸調用每個字節點  
  6. private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)  
  7.         throws XmlPullParserException, IOException {  
  8.   
  9.     final int depth = parser.getDepth();  
  10.     int type;  
  11.   
  12.     while (((type = parser.next()) != XmlPullParser.END_TAG ||  
  13.             parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {  
  14.   
  15.         if (type != XmlPullParser.START_TAG) {  
  16.             continue;  
  17.         }  
  18.         final String name = parser.getName();  
  19.           
  20.         if (TAG_REQUEST_FOCUS.equals(name)) { //處理<requestFocus />標籤  
  21.             parseRequestFocus(parser, parent);  
  22.         } else if (TAG_INCLUDE.equals(name)) { //處理<include />標籤  
  23.             if (parser.getDepth() == 0) {  
  24.                 throw new InflateException("<include /> cannot be the root element");  
  25.             }  
  26.             parseInclude(parser, parent, attrs);//解析<include />節點  
  27.         } else if (TAG_MERGE.equals(name)) { //處理<merge />標籤  
  28.             throw new InflateException("<merge /> must be the root element");  
  29.         } else {  
  30.             //根據節點名構建一個View實例對象  
  31.             final View view = createViewFromTag(name, attrs);   
  32.             final ViewGroup viewGroup = (ViewGroup) parent;  
  33.             //調用generateLayoutParams()方法返回一個LayoutParams實例對象,  
  34.             final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  
  35.             rInflate(parser, view, attrs); //繼續遞歸調用  
  36.             viewGroup.addView(view, params); //OK,將該View以特定LayoutParams值添加至父View中  
  37.         }  
  38.     }  
  39.     parent.onFinishInflate();  //完成了解析過程,通知....  
  40. }  

          值得注意的是,每次addView前都調用了viewGroup.generateLayoutParams(attrs)去構建一個LayoutParams

  實例,然後在addView()方法中爲其賦值。參見如下代碼:ViewGroup.java

  

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ...  
  3.       
  4.     public LayoutParams generateLayoutParams(AttributeSet attrs) {  
  5.         return new LayoutParams(getContext(), attrs);  
  6.     }  
  7.     public static class LayoutParams {  
  8.         ... //會調用這個構造函數  
  9.         public LayoutParams(Context c, AttributeSet attrs) {  
  10.             TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);  
  11.             setBaseAttributes(a,  
  12.                     R.styleable.ViewGroup_Layout_layout_width,  
  13.                     R.styleable.ViewGroup_Layout_layout_height);  
  14.             a.recycle();  
  15.         }  
  16.         protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {  
  17.             width = a.getLayoutDimension(widthAttr, "layout_width");  
  18.             height = a.getLayoutDimension(heightAttr, "layout_height");  
  19.         }  
  20.       
  21. }  

    好吧 ~~ 我們還是探尋根底,去TypeArray類的getLayoutDimension()看看。

         路徑:/frameworks/base/core/java/android/content/res/TypedArray.java

  1. public class TypedArray {  
  2.     ...  
  3.     /** 
  4.      * Special version of {@link #getDimensionPixelSize} for retrieving 
  5.      * {@link android.view.ViewGroup}'s layout_width and layout_height 
  6.      * attributes.  This is only here for performance reasons; applications 
  7.      * should use {@link #getDimensionPixelSize}. 
  8.      *  
  9.      * @param index Index of the attribute to retrieve. 
  10.      * @param name Textual name of attribute for error reporting. 
  11.      *  
  12.      * @return Attribute dimension value multiplied by the appropriate  
  13.      * metric and truncated to integer pixels. 
  14.      */  
  15.     public int getLayoutDimension(int index, String name) {  
  16.         index *= AssetManager.STYLE_NUM_ENTRIES;  
  17.         final int[] data = mData;  
  18.         //獲得屬性對應的標識符 , Identifies,目前還沒有仔細研究相關類。  
  19.         final int type = data[index+AssetManager.STYLE_TYPE];  
  20.         if (type >= TypedValue.TYPE_FIRST_INT  
  21.                 && type <= TypedValue.TYPE_LAST_INT) {  
  22.             return data[index+AssetManager.STYLE_DATA];  
  23.         } else if (type == TypedValue.TYPE_DIMENSION) { //類型爲dimension類型  
  24.             return TypedValue.complexToDimensionPixelSize(  
  25.                 data[index+AssetManager.STYLE_DATA], mResources.mMetrics);  
  26.         }  
  27.         //沒有提供layout_weight和layout_height會來到此處 ,這兒會報異常!  
  28.         //因此佈局文件中的View包括自定義View必須加上屬性layout_weight和layout_height。  
  29.         throw new RuntimeException(getPositionDescription()  
  30.                 + ": You must supply a " + name + " attribute.");  
  31.     }  
  32.     ...  
  33. }  

         從上面得知,  我們將View的AttributeSet屬性傳遞給generateLayoutParams()方法,讓其構建合適地

   LayoutParams對象,並且初始化屬性值weight和height。同時我們也得知 佈局文件中的View包括自定義View

   必須加上屬性layout_weight和layout_height,否則會報異常。


    Step 3 主要做了如下事情:
       首先,獲得了了佈局文件地root View,即佈局文件中最頂層的View。

       其次,通過遞歸調用,我們形成了整個View樹以及設置了每個View的LayoutParams對象。


    

    總結:通過對佈局文件的解析流程的學習,也就是轉換爲View樹的過程,我們明白瞭解析過程的箇中奧妙,以及

設置ViewLayoutParams對象的過程。但是,我們這兒只是簡單的浮光掠影,更深層次的內容希望大家能深入學習。




      本來是準備接下去往下寫的,但無奈貼出來的代碼太多,文章有點長而且自己也有點凌亂了,因此決定做兩篇

  博客發表吧。下篇內容包括如下方面:

        1、MeasureSpec類說明 ;

        2、measure過程中如何正確設置每個View的長寬 ;

        3、UI框架正確設置頂層View的LayoutParams對象,對Activity而言,頂層View則是DecorView,

   其他的皆是普通View了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章