zoukankan      html  css  js  c++  java
  • 【转】Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)

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

    上篇文章<<Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)>>中,我们

      了解了View树的转换过程以及如何设置View的LayoutParams的。本文继续沿着既定轨迹继续未完成的job。

            主要知识点如下:
                     1、MeasureSpc类说明
                     2、measure过程详解(揭秘其细节);
                     3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值得。

           在讲解measure过程前,我们非常有必要理解MeasureSpc类的使用,否则理解起来也只能算是囫囵吞枣。

     

     1、MeasureSpc类说明

     

       1.1  SDK 说明如下

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

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

             a mode.

            即:
                 MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度

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

                ①、UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;

                ②、EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;

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

     

       常用的三个函数:

      static int getMode(int measureSpec)  :  根据提供的测量值(格式)提取模式(上述三个模式之一)

         static int getSize(int measureSpec)  : 根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)

         static int makeMeasureSpec(int size,int mode)  :  根据提供的大小值和模式创建一个测量值(格式)


                 以上摘取自:  <<MeasureSpec介绍及使用详解>>

       1.2   MeasureSpc类源码分析   其为View.java类的内部类,路径:frameworksasecorejavaandroidviewView.java

    1. public class View implements ... {  
    2.      ...  
    3.      public static class MeasureSpec {  
    4.         private static final int MODE_SHIFT = 30; //移位位数为30  
    5.         //int类型占32位,向右移位30位,该属性表示掩码值,用来与size和mode进行"&"运算,获取对应值。  
    6.         private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
    7.   
    8.         //向右移位30位,其值为00 + (30位0)  , 即 0x0000(16进制表示)  
    9.         public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
    10.         //向右移位30位,其值为01 + (30位0)  , 即0x1000(16进制表示)  
    11.         public static final int EXACTLY     = 1 << MODE_SHIFT;  
    12.         //向右移位30位,其值为02 + (30位0)  , 即0x2000(16进制表示)  
    13.         public static final int AT_MOST     = 2 << MODE_SHIFT;  
    14.   
    15.         //创建一个整形值,其高两位代表mode类型,其余30位代表长或宽的实际值。可以是WRAP_CONTENT、MATCH_PARENT或具体大小exactly size  
    16.         public static int makeMeasureSpec(int size, int mode) {  
    17.             return size + mode;  
    18.         }  
    19.         //获取模式  ,与运算  
    20.         public static int getMode(int measureSpec) {  
    21.             return (measureSpec & MODE_MASK);  
    22.         }  
    23.         //获取长或宽的实际值 ,与运算  
    24.         public static int getSize(int measureSpec) {  
    25.             return (measureSpec & ~MODE_MASK);  
    26.         }  
    27.   
    28.     }  
    29.     ...  
    30. }  


        MeasureSpec类的处理思路是:

          ①、右移运算,使int 类型的高两位表示模式的实际值,其余30位表示其余30位代表长或宽的实际值----可以是

             WRAP_CONTENT、MATCH_PARENT或具体大小exactly size。


          ②、通过掩码MODE_MASK进行与运算 “&”,取得模式(mode)以及长或宽(value)的实际值。

     

     2、measure过程详解

     
       2.1  measure过程深入分析

     

           之前的一篇博文<< Android中View绘制流程以及invalidate()等相关方法分析>>,我们从”二B程序员”的角度简单    解了measure过程的调用过程。过了这么多,我们也该升级了,- - 。现在请开始从”普通程序员”角度去理解这个

     过程。我们重点查看measure过程中地相关方法。

         我们说过,当UI框架开始绘制时,皆是从ViewRoot.java类开始绘制的。


          ViewRoot类简要说明: 任何显示在设备中的窗口,例如:Activity、Dialog等,都包含一个ViewRoot实例,该

      类主要用来与远端 WindowManagerService交互以及控制(开始/销毁)绘制。


         Step 1、 开始UI绘制 , 具体绘制方法则是:

    1. 路径:frameworksasecorejavaandroidviewViewRoot.java  
    2. public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {  
    3.     ...  
    4.     //mView对象指添加至窗口的root View ,对Activity窗口而言,则是DecorView对象。  
    5.     View mView;      
    6.       
    7.     //开始View绘制流程  
    8.     private void performTraversals(){  
    9.         ...  
    10.         //这两个值我们在后面讨论时,在回过头来看看是怎么赋值的。现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。  
    11.         int childWidthMeasureSpec; //其值由MeasureSpec类构建 , makeMeasureSpec  
    12.         int childHeightMeasureSpec;//其值由MeasureSpec类构建 , makeMeasureSpec  
    13.           
    14.   
    15.         // Ask host how big it wants to be  
    16.         host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
    17.         ...  
    18.     }  
    19.     ...  
    20. }  


       
          这儿,我并没有说出childWidthMeasureSpec和childHeightMeasureSpec类的来由(为了避免额外地开销,等到
     第三部分时我们在来攻克它,现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。
     

        Step 2 、调用measure()方法去做一些前期准备

           measure()方法原型定义在View.java类中,final修饰符修饰,其不能被重载:

        

    1. public class View implements ... {  
    2.     ...  
    3.     /** 
    4.      * This is called to find out how big a view should be. The parent 
    5.      * supplies constraint information in the width and height parameters. 
    6.      * 
    7.      * @param widthMeasureSpec Horizontal space requirements as imposed by the 
    8.      *        parent 
    9.      * @param heightMeasureSpec Vertical space requirements as imposed by the 
    10.      *        parent 
    11.      * @see #onMeasure(int, int) 
    12.      */  
    13.     public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
    14.         //判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变  
    15.         if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
    16.                 widthMeasureSpec != mOldWidthMeasureSpec ||  
    17.                 heightMeasureSpec != mOldHeightMeasureSpec) {  
    18.   
    19.             // first clears the measured dimension flag  
    20.             //清除MEASURED_DIMENSION_SET标记   ,该标记会在onMeasure()方法后被设置  
    21.             mPrivateFlags &= ~MEASURED_DIMENSION_SET;   
    22.   
    23.             // measure ourselves, this should set the measured dimension flag back  
    24.             // 1、 测量该View本身的大小 ; 2 、 设置MEASURED_DIMENSION_SET标记,否则接写来会报异常。  
    25.             onMeasure(widthMeasureSpec, heightMeasureSpec);  
    26.   
    27.             // flag not set, setMeasuredDimension() was not invoked, we raise  
    28.             // an exception to warn the developer  
    29.             if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
    30.                 throw new IllegalStateException("onMeasure() did not set the"  
    31.                         + " measured dimension by calling" + " setMeasuredDimension()");  
    32.             }  
    33.   
    34.             mPrivateFlags |= LAYOUT_REQUIRED;  //下一步是layout了,添加LAYOUT_REQUIRED标记  
    35.         }  
    36.   
    37.         mOldWidthMeasureSpec = widthMeasureSpec;   //保存值  
    38.         mOldHeightMeasureSpec = heightMeasureSpec; //保存值  
    39.     }  
    40.     ...  
    41. }  

     

          参数widthMeasureSpec和heightMeasureSpec 由父View构建,表示父View给子View的测量要求。其值地构建

     会在下面步骤中详解。  

       measure()方法显示判断是否需要重新调用设置改View大小,即调用onMeasure()方法,然后操作两个标识符:

                ①、重置MEASURED_DIMENSION_SET   : onMeasure()方法中,需要添加该标识符,否则,会报异常;    

           ②、添加LAYOUT_REQUIRED : 表示需要进行layout操作。

        最后,保存当前的widthMeasureSpec和heightMeasureSpec值。

     

       Step 3 、调用onMeasure()方法去真正设置View的长宽值,其默认实现为:

     

    1. /** 
    2.    * Measure the view and its content to determine the measured width and the 
    3.    * measured height. This method is invoked by {@link #measure(int, int)} and 
    4.    * should be overriden by subclasses to provide accurate and efficient 
    5.    * measurement of their contents. 
    6.    *  
    7.    * @param widthMeasureSpec horizontal space requirements as imposed by the parent. 
    8.    *                         The requirements are encoded with 
    9.    * @param heightMeasureSpec vertical space requirements as imposed by the parent. 
    10.    *                         The requirements are encoded with 
    11.    */  
    12.   //设置该View本身地大小  
    13.   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    14.       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
    15.               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    16.   }  
    17.     
    18.   /** 
    19.    * Utility to return a default size. Uses the supplied size if the 
    20.    * MeasureSpec imposed no contraints. Will get larger if allowed 
    21.    * by the MeasureSpec. 
    22.    * 
    23.    * @param size Default size for this view 
    24.    * @param measureSpec Constraints imposed by the parent 
    25.    * @return The size this view should be. 
    26.    */  
    27.   //@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值  
    28.   public static int getDefaultSize(int size, int measureSpec) {  
    29.       int result = size;    
    30.       int specMode = MeasureSpec.getMode(measureSpec);  
    31.       int specSize =  MeasureSpec.getSize(measureSpec);  
    32.   
    33.       //根据不同的mode值,取得宽和高的实际值。  
    34.       switch (specMode) {  
    35.       case MeasureSpec.UNSPECIFIED:  //表示该View的大小父视图未定,设置为默认值  
    36.           result = size;  
    37.           break;  
    38.       case MeasureSpec.AT_MOST:      //表示该View的大小由父视图指定了  
    39.       case MeasureSpec.EXACTLY:  
    40.           result = specSize;  
    41.           break;  
    42.       }  
    43.       return result;  
    44.   }  
    45.   //获得设置了android:minHeight属性或者该View背景图片的大小值, 最为该View的参考值  
    46.   protected int getSuggestedMinimumWidth() {  
    47.       int suggestedMinWidth = mMinWidth;  //  android:minHeight  
    48.   
    49.       if (mBGDrawable != null) { // 背景图片对应地Width。  
    50.           final int bgMinWidth = mBGDrawable.getMinimumWidth();  
    51.           if (suggestedMinWidth < bgMinWidth) {  
    52.               suggestedMinWidth = bgMinWidth;  
    53.           }  
    54.       }  
    55.   
    56.       return suggestedMinWidth;  
    57.   }  
    58.   //设置View在measure过程中宽和高  
    59.   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    60.       mMeasuredWidth = measuredWidth;  
    61.       mMeasuredHeight = measuredHeight;  
    62.   
    63.       mPrivateFlags |= MEASURED_DIMENSION_SET;  //设置了MEASURED_DIMENSION_SET标记  
    64.   }  


           主要功能就是根据该View属性(android:minWidth和背景图片大小)和父View对该子View的"测量要求",设置该      View的 mMeasuredWidth 和 mMeasuredHeight 值。

     

           这儿只是一般的View类型地实现方法。一般来说,父View,也就是ViewGroup类型,都需要在重写onMeasure()   方法,遍历所有子View,设置每个子View的大小。基本思想如下:遍历所有子View,设置每个子View的大小。伪

      代码表示为:

     

    1. //某个ViewGroup类型的视图  
    2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    3.   //必须调用super.ononMeasure()或者直接调用setMeasuredDimension()方法设置该View大小,否则会报异常。  
    4.   super.onMeasure(widthMeasureSpec , heightMeasureSpec)  
    5.      //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
    6.      //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    7.        
    8.   //遍历每个子View  
    9.   for(int i = 0 ; i < getChildCount() ; i++){  
    10.     View child = getChildAt(i);  
    11.     //调用子View的onMeasure,设置他们的大小。childWidthMeasureSpec , childHeightMeasureSpec ?  
    12.     child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  
    13.   }  
    14. }  



          Step 2、Step 3 代码也比较好理解,但问题是我们示例代码中widthMeasureSpec、heightMeasureSpec是如何

     确定的呢?父View是如何设定其值的?

      

          要想回答这个问题,我们看是去源代码里找找答案吧。在ViewGroup.java类中,为我们提供了三个方法,去设置

    个子View的大小,基本思想也如同我们之前描述的思想:遍历所有子View,设置每个子View的大小。

         主要有如下方法:

    1. /** 
    2.  * Ask all of the children of this view to measure themselves, taking into 
    3.  * account both the MeasureSpec requirements for this view and its padding. 
    4.  * We skip children that are in the GONE state The heavy lifting is done in 
    5.  * getChildMeasureSpec. 
    6.  */  
    7. //widthMeasureSpec 和  heightMeasureSpec 表示该父View的布局要求  
    8. //遍历每个子View,然后调用measureChild()方法去实现每个子View大小  
    9. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
    10.     final int size = mChildrenCount;  
    11.     final View[] children = mChildren;  
    12.     for (int i = 0; i < size; ++i) {  
    13.         final View child = children[i];  
    14.         if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不处于 “GONE” 状态  
    15.             measureChild(child, widthMeasureSpec, heightMeasureSpec);  
    16.         }  
    17.     }  
    18. }  
    19.      
    20. /** 
    21.  * Ask one of the children of this view to measure itself, taking into 
    22.  * account both the MeasureSpec requirements for this view and its padding. 
    23.  * The heavy lifting is done in getChildMeasureSpec. 
    24.  * 
    25.  * @param child The child to measure 
    26.  * @param parentWidthMeasureSpec The width requirements for this view 
    27.  * @param parentHeightMeasureSpec The height requirements for this view 
    28.  */  
    29. //测量每个子View高宽时,清楚了该View本身的边距大小,即android:padding属性 或android:paddingLeft等属性标记  
    30. protected void measureChild(View child, int parentWidthMeasureSpec,  
    31.         int parentHeightMeasureSpec) {  
    32.     final LayoutParams lp = child.getLayoutParams(); // LayoutParams属性  
    33.     //设置子View的childWidthMeasureSpec属性,去除了该父View的边距值  mPaddingLeft + mPaddingRight  
    34.     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
    35.             mPaddingLeft + mPaddingRight, lp.width);  
    36.     //设置子View的childHeightMeasureSpec属性,去除了该父View的边距值  mPaddingTop + mPaddingBottom  
    37.     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
    38.             mPaddingTop + mPaddingBottom, lp.height);  
    39.   
    40.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
    41. }  

      

         measureChildren()方法:遍历所有子View,调用measureChild()方法去设置该子View的属性值。

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

     设置子View的实际宽高值。

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

      

    1. /** 
    2.  * Does the hard part of measureChildren: figuring out the MeasureSpec to 
    3.  * pass to a particular child. This method figures out the right MeasureSpec 
    4.  * for one dimension (height or width) of one child view. 
    5.  * 
    6.  * The goal is to combine information from our MeasureSpec with the 
    7.  * LayoutParams of the child to get the best possible results. 
    8.  */  
    9. // spec参数                                    表示该父View本身所占的widthMeasureSpec 或  heightMeasureSpec值  
    10. // padding参数                          表示该父View的边距大小,见于android:padding属性 或android:paddingLeft等属性标记  
    11. // childDimension参数  表示该子View内部LayoutParams属性的值,可以是wrap_content、match_parent、一个精确指(an exactly size),  
    12. //           例如:由android:width指定等。  
    13. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    14.     int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
    15.     int specSize = MeasureSpec.getSize(spec);  //获得父View的实际值  
    16.   
    17.     int size = Math.max(0, specSize - padding); //父View为子View设定的大小,减去边距值,  
    18.   
    19.     int resultSize = 0;    //子View对应地 size 实际值 ,由下面的逻辑条件赋值  
    20.     int resultMode = 0;    //子View对应地 mode 值 , 由下面的逻辑条件赋值  
    21.   
    22.     switch (specMode) {  
    23.     // Parent has imposed an exact size on us  
    24.     //1、父View是EXACTLY的 !  
    25.     case MeasureSpec.EXACTLY:   
    26.         //1.1、子View的width或height是个精确值 (an exactly size)  
    27.         if (childDimension >= 0) {            
    28.             resultSize = childDimension;         //size为精确值  
    29.             resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
    30.         }   
    31.         //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT   
    32.         else if (childDimension == LayoutParams.MATCH_PARENT) {  
    33.             // Child wants to be our size. So be it.  
    34.             resultSize = size;                   //size为父视图大小  
    35.             resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
    36.         }   
    37.         //1.3、子View的width或height为 WRAP_CONTENT  
    38.         else if (childDimension == LayoutParams.WRAP_CONTENT) {  
    39.             // Child wants to determine its own size. It can't be  
    40.             // bigger than us.  
    41.             resultSize = size;                   //size为父视图大小  
    42.             resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。  
    43.         }  
    44.         break;  
    45.   
    46.     // Parent has imposed a maximum size on us  
    47.     //2、父View是AT_MOST的 !      
    48.     case MeasureSpec.AT_MOST:  
    49.         //2.1、子View的width或height是个精确值 (an exactly size)  
    50.         if (childDimension >= 0) {  
    51.             // Child wants a specific size... so be it  
    52.             resultSize = childDimension;        //size为精确值  
    53.             resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。  
    54.         }  
    55.         //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
    56.         else if (childDimension == LayoutParams.MATCH_PARENT) {  
    57.             // Child wants to be our size, but our size is not fixed.  
    58.             // Constrain child to not be bigger than us.  
    59.             resultSize = size;                  //size为父视图大小  
    60.             resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
    61.         }  
    62.         //2.3、子View的width或height为 WRAP_CONTENT  
    63.         else if (childDimension == LayoutParams.WRAP_CONTENT) {  
    64.             // Child wants to determine its own size. It can't be  
    65.             // bigger than us.  
    66.             resultSize = size;                  //size为父视图大小  
    67.             resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
    68.         }  
    69.         break;  
    70.   
    71.     // Parent asked to see how big we want to be  
    72.     //3、父View是UNSPECIFIED的 !  
    73.     case MeasureSpec.UNSPECIFIED:  
    74.         //3.1、子View的width或height是个精确值 (an exactly size)  
    75.         if (childDimension >= 0) {  
    76.             // Child wants a specific size... let him have it  
    77.             resultSize = childDimension;        //size为精确值  
    78.             resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY  
    79.         }  
    80.         //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
    81.         else if (childDimension == LayoutParams.MATCH_PARENT) {  
    82.             // Child wants to be our size... find out how big it should  
    83.             // be  
    84.             resultSize = 0;                        //size为0! ,其值未定  
    85.             resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
    86.         }   
    87.         //3.3、子View的width或height为 WRAP_CONTENT  
    88.         else if (childDimension == LayoutParams.WRAP_CONTENT) {  
    89.             // Child wants to determine its own size.... find out how  
    90.             // big it should be  
    91.             resultSize = 0;                        //size为0! ,其值未定  
    92.             resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
    93.         }  
    94.         break;  
    95.     }  
    96.     //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。  
    97.     return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
    98. }  

           为了便于分析,我将上面的逻辑判断语句使用列表项进行了说明.

     

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


            根据父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View内部

      LayoutParams属性值,共同决定子View的measureSpec值的大小。主要判断条件主要为MeasureSpec的mode

     类型以及LayoutParams的宽高实际值(lp.width,lp.height),见于以上所贴代码中的列表项: 1、 1.1 ; 1.2 ; 1.3 ; 

      2、2.1等。

     

            例如,分析列表3:假设当父View为MeasureSpec.UNSPECIFIED类型,即未定义时,只有当子View的width

     或height指定时,其mode才为MeasureSpec.EXACTLY,否者该View size为 0 ,mode为MeasureSpec.UNSPECIFIED

     ,即处于未指定状态。

          由此可以得出, 每个View大小的设定都事由其父View以及该View共同决定的。但这只是一个期望的大小,每个

     View在测量时最终大小的设定是由setMeasuredDimension()最终决定的。因此,最终确定一个View的“测量长宽“是

     由以下几个方面影响:

            1、父View的MeasureSpec属性;

            2、子View的LayoutParams属性 ;

            3、setMeasuredDimension()或者其它类似设定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

                    setMeasuredDimension()原型:

    1. //设置View在measure过程中宽和高  
    2. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    3.     mMeasuredWidth = measuredWidth;  
    4.     mMeasuredHeight = measuredHeight;  
    5.   
    6.     mPrivateFlags |= MEASURED_DIMENSION_SET;  //设置了MEASURED_DIMENSION_SET标记  
    7. }  

      将上面列表项转换为表格为:

                                  

       这张表格更能帮助我们分析View的MeasureSpec的确定条件关系。

     

       为了帮助大家理解,下面我们分析某个窗口使用地xml布局文件,我们弄清楚该xml布局文件中每个View的

    MeasureSpec值的组成。

        

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


         该布局文件共有两个View:  ①、id为llayout的LinearLayout布局控件 ;

                                                       ②、id为tv的TextView控件。


          假设LinearLayout的父View对应地widthSpec和heightSpec值皆为MeasureSpec.EXACTLY类型(Activity窗口

      的父View为DecorView,具体原因见第三部分说明)。

     

           对LinearLayout而言比较简单,由于 android:layout_width="match_parent",因此其width对应地widthSpec 

      mode值为MeasureSpec.EXACTLY , size由父视图大小指定 ;  由于android:layout_height = "match_parent",

      因此其height对应地heightSpec modeMeasureSpec.EXACTLY,size由父视图大小指定 ;


           对TextView而言 ,其父View为LinearLayout的widthSpec和heightSpec值皆为MeasureSpec.EXACTLY类型,

     由于android:layout_width="match_parent" , 因此其width对应地widthSpec mode值为MeasureSpec.EXACTLY

     size由父视图大小指定 ;  由于android:layout_width="wrap_content" , 因此其height对应地widthSpec mode值为

     MeasureSpec.AT_MOST,size由父视图大小指定 。

     

        我们继续窥测下LinearLayout类是如何进行measure过程的:

    1.  public class LinearLayout extends ViewGroup {  
    2. ...  
    3. @Override  //onMeasure方法。  
    4. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    5.     //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向,  
    6.     if (mOrientation == VERTICAL) {  
    7.         measureVertical(widthMeasureSpec, heightMeasureSpec);  
    8.     } else {  
    9.         measureHorizontal(widthMeasureSpec, heightMeasureSpec);  
    10.     }  
    11. }  
    12. //垂直方向布局  
    13.    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
    14.        mTotalLength = 0;         //该LinearLayout测量子View时的总高度。  
    15.     float totalWeight = 0;    //所有子View的权重和 , android:layout_weight  
    16.     int maxWidth = 0;         //保存子View中最大width值  
    17.        ...  
    18.        final int count = getVirtualChildCount();  //子View的个数  
    19.          
    20.        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
    21.        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
    22.           ...  
    23.        // See how tall everyone is. Also remember max width.  
    24.        for (int i = 0; i < count; ++i) {  
    25.            final View child = getVirtualChildAt(i);  
    26.               ...  
    27.            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
    28.   
    29.            totalWeight += lp.weight;    
    30.            //满足该条件地View会在该LinearLayout有剩余高度时,才真正调用measure()  
    31.            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
    32.                ...  
    33.            } else {  
    34.                int oldHeight = Integer.MIN_VALUE;  
    35.                //如果View的hight值为0,并且设置了android:layout_weight属性,重新纠正其height值为WRAP_CONTENT  
    36.                if (lp.height == 0 && lp.weight > 0) {  
    37.                    oldHeight = 0;  
    38.                    lp.height = LayoutParams.WRAP_CONTENT;  
    39.                }  
    40.                // Determine how big this child would like to be. If this or  
    41.                // previous children have given a weight, then we allow it to  
    42.                // use all available space (and we will shrink things later  
    43.                // if needed).  
    44.                //对每个子View调用measure()方法  
    45.                measureChildBeforeLayout(  
    46.                       child, i, widthMeasureSpec, 0, heightMeasureSpec,  
    47.                       totalWeight == 0 ? mTotalLength : 0);  
    48.                  
    49.                //这三行代码做了如下两件事情:  
    50.                //1、获得该View的measuredHeight值,每个View都会根据他们地属性正确设置值  > 0 ;  
    51.                //2、更新mTotalLength值:取当前高度mTotalLength值与mTotalLength + childHeight 的最大值  
    52.                // 于是对于android:layout_height="wrap_height"属性地LinearLayout控件也就知道了它的确切高度值了。  
    53.                final int childHeight = child.getMeasuredHeight();  
    54.                final int totalLength = mTotalLength;  
    55.                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +  
    56.                       lp.bottomMargin + getNextLocationOffset(child));  
    57.                ...  
    58.            }  
    59.            final int margin = lp.leftMargin + lp.rightMargin;  
    60.            final int measuredWidth = child.getMeasuredWidth() + margin;  
    61.            maxWidth = Math.max(maxWidth, measuredWidth);  
    62.            ...  
    63.        }  
    64.           //后续还有很多处理,包括继续measure()某些符合条件地子View  
    65.        ...  
    66.    }  
    67.    void measureChildBeforeLayout(View child, int childIndex,  
    68.            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,  
    69.            int totalHeight) {  
    70.     //调用measureChildWithMargins()方法去设置子View大小  
    71.        measureChildWithMargins(child, widthMeasureSpec, totalWidth,  
    72.                heightMeasureSpec, totalHeight);  
    73.    }  
    74. ...  


              

            继续看看measureChildWithMargins()方法,该方法定义在ViewGroup.java内,基本流程同于 measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理。

          measureChildWithMargins@ViewGroup.java 

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

        measure()过程时,LinearLayout类做了如下事情 :

                1、遍历每个子View,对其调用measure()方法;

                2、子View measure()完成后,需要取得该子View地宽高实际值,继而做处理(例如:LinearLayout属性为

           android:widht="wrap_content"时,LinearLayout的实际width值则是每个子View的width值的累加值)。

         

      2.2 WRAP_CONTENT、MATCH_PARENT以及measure动机揭秘

     

            子View地宽高实际值 ,即child.getMeasuredWidth()值得返回最终会是一个确定值?  难道WRAP_CONTENT(

    其值为-2) 、MATCH_PARENT(值为-1)或者说一个具体值(an exactly size > 0)。前面我们说过,View最终“测量”值的

    确定是有三个部分组成地:

             ①、父View的MeasureSpec属性;

             ②、子View的LayoutParams属性 ;

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

       因此,一个View必须以某种合适地方法确定它地最终大小。例如,如下自定义View:

    1. //自定义View     
    2. public Class MyView extends View {  
    3.       
    4.      //针对不同地mode值,设置本View地大小  
    5.      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){  
    6.          //获得父View传递给我们地测量需求  
    7.          int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
    8.          int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
    9.            
    10.          int width = 0 ;  
    11.          int height = 0 ;  
    12.          //对UNSPECIFIED 则抛出异常  
    13.          if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)  
    14.              throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED");  
    15.           
    16.          //精确指定  
    17.          if(widthMode == MeasureSpec.EXACTLY){  
    18.              width = 100 ;  
    19.          }  
    20.          //模糊指定  
    21.          else if(widthMode == MeasureSpec.AT_MOST )  
    22.              width = 50 ;   
    23.            
    24.           //精确指定  
    25.          if(heightMode == MeasureSpec.EXACTLY){  
    26.              height = 100 ;  
    27.          }  
    28.          //模糊指定  
    29.          else if(heightMode == MeasureSpec.AT_MOST )  
    30.              height = 50 ;  
    31.            
    32.          setMeasuredDimension(width , height) ;  
    33.      }  
    34. }  

             该自定义View重写了onMeasure()方法,根据传递过来的widthMeasureSpec和heightMeasureSpec简单设置了

     该View的mMeasuredWidth 和 mMeasuredHeight值。

          对于TextView而言,如果它地mode不是Exactly类型 , 它会根据一些属性,例如:android:textStyle

      、android:textSizeandroid:typeface等去确定TextView类地需要占用地长和宽。

       

         因此,如果你地自定义View必须手动对不同mode做出处理。否则,则是mode对你而言是无效的。

       

          Android框架中提供地一系列View/ViewGroup都需要去进行这个measure()过程地 ,因为在layout()过程中,父

      View需要调用getMeasuredWidth()或getMeasuredHeight()去为每个子View设置他们地布局坐标,只有确定布局

      坐标后,才能真正地将该View 绘制(draw)出来,否则该View的layout大小为0,得不到期望效果。我们继续看看

      LinearLayout的layout布局过程:

    1. public class LinearLayout extends ViewGroup {  
    2.     ...  
    3.     @Override  //layout 过程  
    4.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
    5.         //假定是垂直方向布局  
    6.         if (mOrientation == VERTICAL) {  
    7.             layoutVertical();  
    8.         } else {  
    9.             layoutHorizontal();  
    10.         }  
    11.     }  
    12.     //对每个子View调用layout过程  
    13.     void layoutVertical() {  
    14.         ...  
    15.         final int count = getVirtualChildCount();  
    16.         ...  
    17.         for (int i = 0; i < count; i++) {  
    18.             final View child = getVirtualChildAt(i);  
    19.             if (child == null) {  //一般为非null  
    20.                 childTop += measureNullChild(i);  
    21.             } else if (child.getVisibility() != GONE) {  
    22.                 //获得子View测量时的实际宽高值,  
    23.                 final int childWidth = child.getMeasuredWidth();  
    24.                 final int childHeight = child.getMeasuredHeight();  
    25.                   
    26.                 ...  
    27.                 //  封装了child.layout()方法,见如下  
    28.                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),  
    29.                         childWidth, childHeight);   
    30.                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);  
    31.   
    32.                 i += getChildrenSkipCount(child, i);  
    33.             }  
    34.         }  
    35.     }  
    36.     //width = getMeasuredWidth() ; height = childHeight(); View的大小就是测量大小  
    37.     private void setChildFrame(View child, int left, int top, int width, int height) {  
    38.           
    39.         child.layout(left, top, left + width, top + height);  
    40.     }  
    41.     ...  
    42. }     


          对一个View进行measure操作地主要目的就是为了确定该View地布局大小,见上面所示代码。但measure操作

     通常是耗时的,因此对自定义ViewGroup而言,我们可以自由控制measure、layout过程,如果我们知道如何layout

     一个View,我们可以跳过该ViewGroup地measure操作(onMeasure()方法中measure所有子View地),直接去layout

     

          在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明>>中,我们自定义了一个     ViewGroup,  并且重写了onMeasure()和onLayout()方法去分别操作每个View。就该ViewGroup而言,我们只需要

      重写onLayout()操作即可,因为我们知道如何layout每个子View。如下代码所示:

     

    1. //自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置  
    2. public class MultiViewGroup extends ViewGroup {  
    3.     private void init() {  
    4.         // 初始化3个 LinearLayout控件  
    5.         LinearLayout oneLL = new LinearLayout(mContext);  
    6.         oneLL.setBackgroundColor(Color.RED);  
    7.         addView(oneLL);  
    8.         ...  
    9.     }  
    10.     @Override  
    11.     // 我们知晓每个子View的layout布局大小,因此我们不需要为每个子View进行measure()操作了。  
    12. //  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    13. //      setMeasuredDimension(width, height);  
    14. //      // 设置该ViewGroup的大小  
    15. //      int width = MeasureSpec.getSize(widthMeasureSpec);  
    16. //      int height = MeasureSpec.getSize(heightMeasureSpec);  
    17. //      int childCount = getChildCount();  
    18. //      for (int i = 0; i < childCount; i++) {  
    19. //          View child = getChildAt(i);  
    20. //          // 设置每个子视图的大小 , 即全屏  
    21. //          child.measure(MultiScreenActivity.screenWidth, MultiScreenActivity.scrrenHeight);  
    22. //      }  
    23.     }  
    24.   
    25.     // layout过程  
    26.     @Override  
    27.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
    28.         // TODO Auto-generated method stub  
    29.         Log.i(TAG, "--- start onLayout --");  
    30.         int startLeft = 0; // 每个子视图的起始布局坐标  
    31.         int startTop = 10; // 间距设置为10px 相当于 android:marginTop= "10px"  
    32.         int childCount = getChildCount();  
    33.         Log.i(TAG, "--- onLayout childCount is -->" + childCount);  
    34.         for (int i = 0; i < childCount; i++) {  
    35.             View child = getChildAt(i);  
    36.             child.layout(startLeft, startTop,   
    37.                     startLeft + MultiScreenActivity.screenWidth,   
    38.                     startTop + MultiScreenActivity.scrrenHeight);  
    39.             startLeft = startLeft + MultiScreenActivity.screenWidth ; //校准每个子View的起始布局位置  
    40.             //三个子视图的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960]  
    41.         }  
    42.     }  
    43. }    


         更多关于自定义ViewGroup无须重写measure动作的,可以参考 Android API :

                   <<Optimizing the View >>

         中文翻译见于:<< Android中View绘制优化之三---- 优化View>>


     3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值

     
             老子道德经有言:“道生一,一生二,二生三,三生万物。”  UI绘制也就是个递归过程。理解其基本架构后,
     也就“掌握了一个中心点”了。在第一节中,我们没有说明开始UI绘制时 ,没有说明mView.measure()参数地由来,
     参数也就是我们本节需要弄懂的“道” --- root View的 widthMeasureSpec和heightMeasureSpec 是如何确定的。
     
       对于如下布局文件: main.xml
    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:orientation="vertical"  
    4.     android:layout_width="fill_parent"  
    5.     android:layout_height="fill_parent"  
    6.     >  
    7. <TextView    
    8.     android:layout_width="fill_parent"   
    9.     android:layout_height="wrap_content"   
    10.     android:text="@string/hello"  
    11.     />  
    12. </LinearLayout>  
      
        当使用LayoutInflater类解析成View时 ,LinearLayout对象的LayoutParams参数为null 。具体原因请参考上篇博文
     
        任何一个View被添加至窗口时,都需要利用WindowManager类去操作。例如,如下代码:
     
    1. //显示一个悬浮窗吧 , just so so   
    2. public void showView()  
    3. {  
    4.     //解析布局文件  
    5.     LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
    6.     //rootView对应地LayoutParams属性值为null,将会在UI绘制时设定其值  
    7.     View rootView = layoutInflater.inflate(R.layout.main, null);  
    8.       
    9.     WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);  
    10.     //设置WindowManager.LayoutParams参数值,作为该窗口的各种属性  
    11.     WindowManager.LayoutParams winparams = WindowManager.LayoutParams();  
    12.      // 以屏幕左上角为原点,设置x、y初始值  
    13.     winparams.x = 0;  
    14.     winparams.y = 0;  
    15.   
    16.     //设置悬浮窗口长宽数据  
    17.     winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;;  
    18.     winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;;  
    19.        
    20.     windowManager.addView(rootView, winparams);  
    21. }  
    1.   
           关于WindowManager的使用请看如下博客 :
                                   <<android学习---- WindowManager 接口 >>
                                  <<在Android中使用WindowManager实现悬浮窗口>>
          关于WindowManager.LayoutParams类说明请看如下博客: 
                                  << android学习---- WindowManager.LayoutParams>>
     
           下面,我们从获得WindowManager对象引用开始,一步步观察addView()做了一些什么事情。
     
     
     
       Step 1 、获得WindowManager对象服务 ,具体实现类在ContextImpl.java内中
               路径: /frameworks/base/core/java/android/app/ContextImpl.java         
    1. @Override  
    2. public Object getSystemService(String name) {  
    3.     if (WINDOW_SERVICE.equals(name)) {  
    4.         return WindowManagerImpl.getDefault();  
    5.     }  
    6.     ...  
    7. }  
            WindowManager是个接口,具体返回对象则是WindowManagerImpl的单例对象。
     
     Step 2 、 获得WindowManagerImpl的单例对象,以及部分源码分析
               路径: /frameworks/base/core/java/android/view/WindowManagerImpl.java 
    1. public class WindowManagerImpl implements WindowManager{  
    2.          
    3.    public static WindowManagerImpl getDefault()  
    4.    {  
    5.        return mWindowManager;  
    6.    }  
    7.    //以特定Window属性添加一个窗口  
    8.    public void addView(View view, ViewGroup.LayoutParams params)  
    9.    {  
    10.        addView(view, params, false);  
    11.    }  
    12.    //参数nest表示该窗口是不是一个字窗口  
    13.    private void addView(View view, ViewGroup.LayoutParams params, boolean nest)  
    14.    {   ...  
    15.        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;  
    16.          
    17.        ViewRoot root;  
    18.        View panelParentView = null;    //该子窗口对应地父窗口View  
    19.          
    20.        synchronized (this) {  
    21.             
    22.            ...//需要对传递过来地参数进行检测...  
    23.              
    24.            //对每个窗口皆构建一个ViewRoot对象  
    25.            root = new ViewRoot(view.getContext());  
    26.            root.mAddNesting = 1;  
    27.            //设置root View 的LayoutParams为wparams,即WindowManager.LayoutParams类型  
    28.            view.setLayoutParams(wparams);  
    29.            ...//对参数检测,以及拷贝原有数组...  
    30.              
    31.            //将窗口对应地view、root、wparams保存在属性集合中  
    32.            mViews[index] = view;  
    33.            mRoots[index] = root;  
    34.            mParams[index] = wparams;  
    35.        }  
    36.        // do this last because it fires off messages to start doing things  
    37.        // 调用ViewRoot对象去通知系统添加一个窗口  
    38.        root.setView(view, wparams, panelParentView);  
    39.    }  
    40.    ...  
    41.    //这三个数组分别保存了一个窗口对应地属性  
    42.    private View[] mViews;         //root View对象 , View类型  
    43.    private ViewRoot[] mRoots;     //ViewRoot类型 , 与WMS通信  
    44.    private WindowManager.LayoutParams[] mParams;  //窗口属性  
    45.      
    46.    //WindowManagerImpl实现了单例模式  
    47.    private static WindowManagerImpl mWindowManager = new WindowManagerImpl();  
    48. }  
     

          WindowManagerImpl类的三个数组集合保存了每个窗口相关属性,这样我们可以通过这些属性去操作特定的
     窗口(例如,可以根据View去更新/销毁该窗口)。当参数检查成功时,构建一个ViewRoot对象,并且设置设置root
     View 的LayoutParams为wparams,即WindowManager.LayoutParams类型。最后调用root.setView()方法去通知
     系统需要创建该窗口。我们接下来往下看看ViewRoot类相关操作。
      
        Step 3、
       
    1. public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {  
    2.          
    3.     View mView;   //所有窗口地root View     
    4.     final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();    
    5.   
    6.     ...  
    7.      /** 
    8.      * We have one child 
    9.      */  
    10.     public void setView(View view, WindowManager.LayoutParams attrs,  
    11.             View panelParentView) {  
    12.         synchronized (this) {  
    13.             if (mView == null) {  
    14.                 mView = view;  
    15.                 mWindowAttributes.copyFrom(attrs); //保存WindowManager.LayoutParams属性值  
    16.                 attrs = mWindowAttributes;  
    17.                 ...  
    18.                   
    19.                 mAdded = true;  
    20.                 int res; /* = WindowManagerImpl.ADD_OKAY; */  
    21.   
    22.                 // Schedule the first layout -before- adding to the window  
    23.                 // manager, to make sure we do the relayout before receiving  
    24.                 // any other events from the system.  
    25.                 requestLayout();   //请求UI开始绘制。  
    26.                 mInputChannel = new InputChannel();  //创建一个InputChannel对象,接受消息  
    27.                 try {  
    28.                     //通知WindowManagerService添加一个窗口  
    29.                     res = sWindowSession.add(mWindow, mWindowAttributes,  
    30.                             getHostVisibility(), mAttachInfo.mContentInsets,  
    31.                             mInputChannel);  
    32.                 }   
    33.                 ...  
    34.                 view.assignParent(this);  //将root View的父View设置为该ViewRoot对象(实现了ViewParent接口)  
    35.                 ...  
    36.             }  
    37.         }  
    38.     }  
    39. }  
               说明:ViewRoot类继承了Handler,实现了ViewParent接口
     
      setView()方法地主要功能如下:
            1、保存相关属性值,例如:mView、mWindowAttributes等;
            2、调用requestLayout()方法请求UI绘制,由于ViewRoot是个Handler对象,异步请求;
            3、通知WindowManagerService添加一个窗口;
            4、注册一个事件监听管道,用来监听:按键(KeyEvent)和触摸(MotionEvent)事件。
      我们这儿重点关注 requestLayout()方法请求UI绘制地流程。
     
      Step 4、异步调用请求UI绘制
       
    1. /** 
    2.  * {@inheritDoc} 
    3.  */  
    4. public void requestLayout() {  
    5.     checkThread();        //检查是不是UI线程调用,如果不是UI线程,会报异常  
    6.     mLayoutRequested = true;   //置为真,表示需要进行measure和layout过程  
    7.     scheduleTraversals();    
    8. }  
    9. //开始UI绘制流程  
    10. public void scheduleTraversals() {  
    11.     if (!mTraversalScheduled) {  
    12.         mTraversalScheduled = true;       //防止多次调用  
    13.         sendEmptyMessage(DO_TRAVERSAL);   //异步请求UI绘制  
    14.     }  
    15. }  
    16. @Override  
    17. public void handleMessage(Message msg) {  
    18.  switch (msg.what) {  
    19.         case DO_TRAVERSAL:  
    20.              performTraversals();  //开始UI绘制  
    21.              break;  
    22.  }  
    23. }  
       
              由于performTraversals()方法比较复杂,我们侧重于第一次设置root View的widhtSpecSize以及    
      heightSpecSize值。
    1. private void performTraversals() {  
    2.     // cache mView since it is used so much below...  
    3.     final View host = mView;  
    4.   
    5.     mTraversalScheduled = false;           
    6.     boolean surfaceChanged = false;  
    7.     WindowManager.LayoutParams lp = mWindowAttributes;    
    8.   
    9.     int desiredWindowWidth;              //表示该窗口期望width值  
    10.     int desiredWindowHeight;             //表示该窗口期望width值  
    11.     int childWidthMeasureSpec;           //保存root View的widthMeasureSpec  
    12.     int childHeightMeasureSpec;          //保存root View的heightMeasureSpec  
    13.   
    14.     final View.AttachInfo attachInfo = mAttachInfo;  
    15.   
    16.     final int viewVisibility = getHostVisibility();  
    17.     boolean viewVisibilityChanged = mViewVisibility != viewVisibility  
    18.             || mNewSurfaceNeeded;  
    19.   
    20.     float appScale = mAttachInfo.mApplicationScale;  
    21.   
    22.     WindowManager.LayoutParams params = null;  
    23.     if (mWindowAttributesChanged) {  
    24.         mWindowAttributesChanged = false;  
    25.         surfaceChanged = true;  
    26.         params = lp;  
    27.     }  
    28.     Rect frame = mWinFrame;  
    29.     if (mFirst) {   //mFirst表示是否是第一次绘制该Window  
    30.         fullRedrawNeeded = true;  
    31.         mLayoutRequested = true;  
    32.   
    33.         DisplayMetrics packageMetrics =  
    34.             mView.getContext().getResources().getDisplayMetrics();  
    35.         //第一次绘制时desiredWindowWidth,desiredWindowHeight 值大小为屏幕大小  
    36.         desiredWindowWidth = packageMetrics.widthPixels;  
    37.         desiredWindowHeight = packageMetrics.heightPixels;  
    38.         ...  
    39.     } else {   //不是第一次绘制,则desiredWindowWidth值为frame保存大小,frame值会由WMS填充  
    40.         desiredWindowWidth = frame.width();  
    41.         desiredWindowHeight = frame.height();  
    42.         ...  
    43.     }  
    44.     ...  
    45.     boolean insetsChanged = false;  
    46.   
    47.     if (mLayoutRequested) {  
    48.         ...//获得root View的widthMeasureSpec 和 heightMeasureSpec值  
    49.         childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
    50.         childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
    51.         //开始measure过程  
    52.         host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
    53.     }  
    54.     ...  
    55.     final boolean didLayout = mLayoutRequested;  
    56.       
    57.     boolean triggerGlobalLayoutListener = didLayout  
    58.             || attachInfo.mRecomputeGlobalAttributes;  
    59.     if (didLayout) {  
    60.         ... //layout过程  
    61.        host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);  
    62.         ...  
    63.     }  
    64.     ...  
    65.     if (!cancelDraw && !newSurface) {  
    66.         mFullRedrawNeeded = false;  
    67.         draw(fullRedrawNeeded);  
    68.         ...  
    69. }  
    1. /** 
    2.   * @param windowSize  The available width or height of the window 
    3.   * 
    4.   * @param rootDimension The layout params for one dimension (width or height) of the window. 
    5.  */  
    6.  private int getRootMeasureSpec(int windowSize, int rootDimension) {  
    7.      int measureSpec;  
    8.      switch (rootDimension) {  
    9.      case ViewGroup.LayoutParams.MATCH_PARENT:  
    10.          // Window can't resize. Force root view to be windowSize.  
    11.          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
    12.          break;  
    13.      case ViewGroup.LayoutParams.WRAP_CONTENT:  
    14.          // Window can resize. Set max size for root view.  
    15.          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
    16.          break;  
    17.      default:  
    18.          // Window wants to be an exact size. Force root view to be that size.  
    19.          measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
    20.          break;  
    21.      }  
    22.      return measureSpec;  
    23.  }         


          调用root View的measure()方法时,其参数是由getRootMeasureSpec()设置的,基本思路同我们前面描述的
      差不多。贴出来的代码只是简简单单列出了measure 、layout 、 draw 过程的调用点,里面有很多逻辑处理,
      阅读起来比较费劲,我也只能算是个囫囵吞枣水平。大家有兴趣地可以看看源码,加深理解。
     
        
     
        最后,由于小子理解水平有限,可能很多地方让大家“丈二和尚--摸不着头脑”,给大家两个小建议吧:
                1、仔细钻研源码  ;
                2、想认真系统性研读UI绘制原理的话,建议详细阅读<<Android内核剖析>>第十三章 <UI绘制原理>
    每个人的强大都是从弱小开始慢慢积累起来的!!
  • 相关阅读:
    Three Algorithms for Fibonacci
    微软面试经历
    [TIP]命令行快速查看图片(Ubuntu)
    emacs as the c++ ide on the Ubuntu
    boost learn notes
    ReadingNotes@02122013
    ignoreunderline.org
    cnblogsminormode.org
    c++ 0x 新特性
    noip模拟赛 思考熊的马拉松
  • 原文地址:https://www.cnblogs.com/gaoanchen/p/4457668.html
Copyright © 2011-2022 走看看