• 【转】Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)


     转载请注明出处:http://blog.csdn.net/qinjuning

    在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>>,简单的阐述 了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。

            路径:frameworksasecorejavaandroidviewView.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源码分析

          路径位于:frameworksasecorejavaandroidviewViewGroup.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的过程:

             路径:frameworksasecorejavaandroidviewViewGroup.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方向的单元格索引和高度等。

                路径: packagesappsLauncher2srccomandroidlauncher2CellLayout.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的引用。

             路径:frameworksasecorejavaandroidappContextImpl.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查询对应函数,看看内部实现。    

               径:frameworksasecorejavacomandroidinternalpolicyPolicyManager.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了。

     

     



    每个人的强大都是从弱小开始慢慢积累起来的!!
  • 相关阅读:
    Android 异步请求通用类
    Android 异步下载
    Eclipse 使用 VS Emulator for android 调试环境配置 步骤
    android ListView 可缩放,支持左右上下手势
    安卓中自定义控件引用
    java中的可释放资源定义,类似c#中的using
    java 实现自定义事件
    c# android 全局捕获未处理异常
    java android 捕获未处理异常
    java 中异常处理示例并捕获完整异常内容
  • 原文地址:https://www.cnblogs.com/gaoanchen/p/4457661.html
Copyright © 2020-2023  润新知