zoukankan      html  css  js  c++  java
  • 【转】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了。

     

     



    每个人的强大都是从弱小开始慢慢积累起来的!!
  • 相关阅读:
    微人事项目-mybatis-持久层
    通过外键连接多个表
    springioc
    Redis 消息中间件 ServiceStack.Redis 轻量级
    深度数据对接 链接服务器 数据传输
    sqlserver 抓取所有执行语句 SQL语句分析 死锁 抓取
    sqlserver 索引优化 CPU占用过高 执行分析 服务器检查
    sql server 远程备份 bak 删除
    冒泡排序
    多线程 异步 beginInvoke EndInvoke 使用
  • 原文地址:https://www.cnblogs.com/gaoanchen/p/4457661.html
Copyright © 2011-2022 走看看