Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)

来源:互联网 发布:js下装打什么宝珠 编辑:程序博客网 时间:2024/05/17 04:11

转载出处:http://blog.csdn.net/qinjuning/article/details/8074262


上篇文章<<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类的内部类,路径:\frameworks\base\core\java\android\view\View.java


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

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绘制 , 具体绘制方法则是: 
     路径:\frameworks\base\core\java\android\view\ViewRoot.java  


public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {      ...      //mView对象指添加至窗口的root View ,对Activity窗口而言,则是DecorView对象。      View mView;                //开始View绘制流程      private void performTraversals(){          ...          //这两个值我们在后面讨论时,在回过头来看看是怎么赋值的。现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。          int childWidthMeasureSpec; //其值由MeasureSpec类构建 , makeMeasureSpec          int childHeightMeasureSpec;//其值由MeasureSpec类构建 , makeMeasureSpec                      // Ask host how big it wants to be          host.measure(childWidthMeasureSpec, childHeightMeasureSpec);          ...      }      ...  }  
这儿,我并没有说出childWidthMeasureSpec和childHeightMeasureSpec类的来由(为了避免额外地开销,等到
 第三部分时我们在来攻克它,现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。

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

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

public class View implements ... {      ...      /**      * This is called to find out how big a view should be. The parent      * supplies constraint information in the width and height parameters.      *      * @param widthMeasureSpec Horizontal space requirements as imposed by the      *        parent      * @param heightMeasureSpec Vertical space requirements as imposed by the      *        parent      * @see #onMeasure(int, int)      */      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {          //判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变          if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||                  widthMeasureSpec != mOldWidthMeasureSpec ||                  heightMeasureSpec != mOldHeightMeasureSpec) {                // first clears the measured dimension flag              //清除MEASURED_DIMENSION_SET标记   ,该标记会在onMeasure()方法后被设置              mPrivateFlags &= ~MEASURED_DIMENSION_SET;                 // measure ourselves, this should set the measured dimension flag back              // 1、 测量该View本身的大小 ; 2 、 设置MEASURED_DIMENSION_SET标记,否则接写来会报异常。              onMeasure(widthMeasureSpec, heightMeasureSpec);                // flag not set, setMeasuredDimension() was not invoked, we raise              // an exception to warn the developer              if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {                  throw new IllegalStateException("onMeasure() did not set the"                          + " measured dimension by calling" + " setMeasuredDimension()");              }                mPrivateFlags |= LAYOUT_REQUIRED;  //下一步是layout了,添加LAYOUT_REQUIRED标记          }            mOldWidthMeasureSpec = widthMeasureSpec;   //保存值          mOldHeightMeasureSpec = heightMeasureSpec; //保存值      }      ...  }  

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

 会在下面步骤中详解。  

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

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

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

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


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


/**    * Measure the view and its content to determine the measured width and the    * measured height. This method is invoked by {@link #measure(int, int)} and    * should be overriden by subclasses to provide accurate and efficient    * measurement of their contents.    *     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.    *                         The requirements are encoded with    * @param heightMeasureSpec vertical space requirements as imposed by the parent.    *                         The requirements are encoded with    */    //设置该View本身地大小    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }        /**    * Utility to return a default size. Uses the supplied size if the    * MeasureSpec imposed no contraints. Will get larger if allowed    * by the MeasureSpec.    *    * @param size Default size for this view    * @param measureSpec Constraints imposed by the parent    * @return The size this view should be.    */    //@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值    public static int getDefaultSize(int size, int measureSpec) {        int result = size;          int specMode = MeasureSpec.getMode(measureSpec);        int specSize =  MeasureSpec.getSize(measureSpec);          //根据不同的mode值,取得宽和高的实际值。        switch (specMode) {        case MeasureSpec.UNSPECIFIED:  //表示该View的大小父视图未定,设置为默认值            result = size;            break;        case MeasureSpec.AT_MOST:      //表示该View的大小由父视图指定了        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result;    }    //获得设置了android:minHeight属性或者该View背景图片的大小值, 最为该View的参考值    protected int getSuggestedMinimumWidth() {        int suggestedMinWidth = mMinWidth;  //  android:minHeight          if (mBGDrawable != null) { // 背景图片对应地Width。            final int bgMinWidth = mBGDrawable.getMinimumWidth();            if (suggestedMinWidth < bgMinWidth) {                suggestedMinWidth = bgMinWidth;            }        }          return suggestedMinWidth;    }    //设置View在measure过程中宽和高    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;          mPrivateFlags |= MEASURED_DIMENSION_SET;  //设置了MEASURED_DIMENSION_SET标记    }  

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


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

  代码表示为:

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


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

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

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

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

     主要有如下方法:



/**  * Ask all of the children of this view to measure themselves, taking into  * account both the MeasureSpec requirements for this view and its padding.  * We skip children that are in the GONE state The heavy lifting is done in  * getChildMeasureSpec.  */  //widthMeasureSpec 和  heightMeasureSpec 表示该父View的布局要求  //遍历每个子View,然后调用measureChild()方法去实现每个子View大小  protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {      final int size = mChildrenCount;      final View[] children = mChildren;      for (int i = 0; i < size; ++i) {          final View child = children[i];          if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不处于 “GONE” 状态              measureChild(child, widthMeasureSpec, heightMeasureSpec);          }      }  }       /**  * Ask one of the children of this view to measure itself, taking into  * account both the MeasureSpec requirements for this view and its padding.  * The heavy lifting is done in getChildMeasureSpec.  *  * @param child The child to measure  * @param parentWidthMeasureSpec The width requirements for this view  * @param parentHeightMeasureSpec The height requirements for this view  */  //测量每个子View高宽时,清楚了该View本身的边距大小,即android:padding属性 或android:paddingLeft等属性标记  protected void measureChild(View child, int parentWidthMeasureSpec,          int parentHeightMeasureSpec) {      final LayoutParams lp = child.getLayoutParams(); // LayoutParams属性      //设置子View的childWidthMeasureSpec属性,去除了该父View的边距值  mPaddingLeft + mPaddingRight      final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,              mPaddingLeft + mPaddingRight, lp.width);      //设置子View的childHeightMeasureSpec属性,去除了该父View的边距值  mPaddingTop + mPaddingBottom      final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,              mPaddingTop + mPaddingBottom, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  }  

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

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

 设置子View的实际宽高值。

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


/**  * Does the hard part of measureChildren: figuring out the MeasureSpec to  * pass to a particular child. This method figures out the right MeasureSpec  * for one dimension (height or width) of one child view.  *  * The goal is to combine information from our MeasureSpec with the  * LayoutParams of the child to get the best possible results.  */  // spec参数                                    表示该父View本身所占的widthMeasureSpec 或  heightMeasureSpec值  // padding参数                          表示该父View的边距大小,见于android:padding属性 或android:paddingLeft等属性标记  // childDimension参数  表示该子View内部LayoutParams属性的值,可以是wrap_content、match_parent、一个精确指(an exactly size),  //           例如:由android:width指定等。  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {      int specMode = MeasureSpec.getMode(spec);  //获得父View的mode      int specSize = MeasureSpec.getSize(spec);  //获得父View的实际值        int size = Math.max(0, specSize - padding); //父View为子View设定的大小,减去边距值,        int resultSize = 0;    //子View对应地 size 实际值 ,由下面的逻辑条件赋值      int resultMode = 0;    //子View对应地 mode 值 , 由下面的逻辑条件赋值        switch (specMode) {      // Parent has imposed an exact size on us      //1、父View是EXACTLY的 !      case MeasureSpec.EXACTLY:           //1.1、子View的width或height是个精确值 (an exactly size)          if (childDimension >= 0) {                        resultSize = childDimension;         //size为精确值              resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。          }           //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT           else if (childDimension == LayoutParams.MATCH_PARENT) {              // Child wants to be our size. So be it.              resultSize = size;                   //size为父视图大小              resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。          }           //1.3、子View的width或height为 WRAP_CONTENT          else if (childDimension == LayoutParams.WRAP_CONTENT) {              // Child wants to determine its own size. It can't be              // bigger than us.              resultSize = size;                   //size为父视图大小              resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。          }          break;        // Parent has imposed a maximum size on us      //2、父View是AT_MOST的 !          case MeasureSpec.AT_MOST:          //2.1、子View的width或height是个精确值 (an exactly size)          if (childDimension >= 0) {              // Child wants a specific size... so be it              resultSize = childDimension;        //size为精确值              resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。          }          //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT          else if (childDimension == LayoutParams.MATCH_PARENT) {              // Child wants to be our size, but our size is not fixed.              // Constrain child to not be bigger than us.              resultSize = size;                  //size为父视图大小              resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST          }          //2.3、子View的width或height为 WRAP_CONTENT          else if (childDimension == LayoutParams.WRAP_CONTENT) {              // Child wants to determine its own size. It can't be              // bigger than us.              resultSize = size;                  //size为父视图大小              resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST          }          break;        // Parent asked to see how big we want to be      //3、父View是UNSPECIFIED的 !      case MeasureSpec.UNSPECIFIED:          //3.1、子View的width或height是个精确值 (an exactly size)          if (childDimension >= 0) {              // Child wants a specific size... let him have it              resultSize = childDimension;        //size为精确值              resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY          }          //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT          else if (childDimension == LayoutParams.MATCH_PARENT) {              // Child wants to be our size... find out how big it should              // be              resultSize = 0;                        //size为0! ,其值未定              resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED          }           //3.3、子View的width或height为 WRAP_CONTENT          else if (childDimension == LayoutParams.WRAP_CONTENT) {              // Child wants to determine its own size.... find out how              // big it should be              resultSize = 0;                        //size为0! ,其值未定              resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED          }          break;      }      //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  }  

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()原型:


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

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




<p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 18px; font-family: 'Comic Sans MS';">这张表格更能帮助我们分析View的MeasureSpec的确定条件关系。</span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-family: 'Comic Sans MS'; font-size: 18px;"></span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-family: 'Comic Sans MS'; font-size: 18px;">   为了帮助大家理解,下面我们分析某个窗口使用地xml布局文件,我们弄清楚该xml布局文件中每个View的</span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-family: 'Comic Sans MS'; font-size: 18px;">MeasureSpec值的组成。</span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-family: 'Comic Sans MS'; font-size: 18px;">    </span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-family: 'Comic Sans MS'; font-size: 18px;"></span><pre name="code" class="java"><?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:id="@+id/llayout"         android:orientation="vertical"       android:layout_width="match_parent"         android:layout_height="match_parent">                  <TextView android:id="@+id/tv"           android:layout_width="match_parent"          android:layout_height="wrap_content"          android:text="@string/hello" />    </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由父视图大小指定 ; 


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由父视图大小指定 。


public class LinearLayout extends ViewGroup {  ...  @Override  //onMeasure方法。  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {      //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向,      if (mOrientation == VERTICAL) {          measureVertical(widthMeasureSpec, heightMeasureSpec);      } else {          measureHorizontal(widthMeasureSpec, heightMeasureSpec);      }  }  //垂直方向布局     void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {         mTotalLength = 0;         //该LinearLayout测量子View时的总高度。      float totalWeight = 0;    //所有子View的权重和 , android:layout_weight      int maxWidth = 0;         //保存子View中最大width值         ...         final int count = getVirtualChildCount();  //子View的个数                  final int widthMode = MeasureSpec.getMode(widthMeasureSpec);         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);            ...         // See how tall everyone is. Also remember max width.         for (int i = 0; i < count; ++i) {             final View child = getVirtualChildAt(i);                ...             LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();               totalWeight += lp.weight;               //满足该条件地View会在该LinearLayout有剩余高度时,才真正调用measure()             if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {                 ...             } else {                 int oldHeight = Integer.MIN_VALUE;                 //如果View的hight值为0,并且设置了android:layout_weight属性,重新纠正其height值为WRAP_CONTENT                 if (lp.height == 0 && lp.weight > 0) {                     oldHeight = 0;                     lp.height = LayoutParams.WRAP_CONTENT;                 }                 // Determine how big this child would like to be. If this or                 // previous children have given a weight, then we allow it to                 // use all available space (and we will shrink things later                 // if needed).                 //对每个子View调用measure()方法                 measureChildBeforeLayout(                        child, i, widthMeasureSpec, 0, heightMeasureSpec,                        totalWeight == 0 ? mTotalLength : 0);                                  //这三行代码做了如下两件事情:                 //1、获得该View的measuredHeight值,每个View都会根据他们地属性正确设置值  > 0 ;                 //2、更新mTotalLength值:取当前高度mTotalLength值与mTotalLength + childHeight 的最大值                 // 于是对于android:layout_height="wrap_height"属性地LinearLayout控件也就知道了它的确切高度值了。                 final int childHeight = child.getMeasuredHeight();                 final int totalLength = mTotalLength;                 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +                        lp.bottomMargin + getNextLocationOffset(child));                 ...             }             final int margin = lp.leftMargin + lp.rightMargin;             final int measuredWidth = child.getMeasuredWidth() + margin;             maxWidth = Math.max(maxWidth, measuredWidth);             ...         }            //后续还有很多处理,包括继续measure()某些符合条件地子View         ...     }     void measureChildBeforeLayout(View child, int childIndex,             int widthMeasureSpec, int totalWidth, int heightMeasureSpec,             int totalHeight) {      //调用measureChildWithMargins()方法去设置子View大小         measureChildWithMargins(child, widthMeasureSpec, totalWidth,                 heightMeasureSpec, totalHeight);     }  ...  


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

      measureChildWithMargins@ViewGroup.java 


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

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:


//自定义View     public Class MyView extends View {             //针对不同地mode值,设置本View地大小       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){           //获得父View传递给我们地测量需求           int widthMode = MeasureSpec.getMode(widthMeasureSpec);           int heightMode = MeasureSpec.getMode(heightMeasureSpec);                      int width = 0 ;           int height = 0 ;           //对UNSPECIFIED 则抛出异常           if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)               throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED");                     //精确指定           if(widthMode == MeasureSpec.EXACTLY){               width = 100 ;           }           //模糊指定           else if(widthMode == MeasureSpec.AT_MOST )               width = 50 ;                        //精确指定           if(heightMode == MeasureSpec.EXACTLY){               height = 100 ;           }           //模糊指定           else if(heightMode == MeasureSpec.AT_MOST )               height = 50 ;                      setMeasuredDimension(width , height) ;       }  }  

  该自定义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布局过程:


public class LinearLayout extends ViewGroup {      ...      @Override  //layout 过程      protected void onLayout(boolean changed, int l, int t, int r, int b) {          //假定是垂直方向布局          if (mOrientation == VERTICAL) {              layoutVertical();          } else {              layoutHorizontal();          }      }      //对每个子View调用layout过程      void layoutVertical() {          ...          final int count = getVirtualChildCount();          ...          for (int i = 0; i < count; i++) {              final View child = getVirtualChildAt(i);              if (child == null) {  //一般为非null                  childTop += measureNullChild(i);              } else if (child.getVisibility() != GONE) {                  //获得子View测量时的实际宽高值,                  final int childWidth = child.getMeasuredWidth();                  final int childHeight = child.getMeasuredHeight();                                    ...                  //  封装了child.layout()方法,见如下                  setChildFrame(child, childLeft, childTop + getLocationOffset(child),                          childWidth, childHeight);                   childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);                    i += getChildrenSkipCount(child, i);              }          }      }      //width = getMeasuredWidth() ; height = childHeight(); View的大小就是测量大小      private void setChildFrame(View child, int left, int top, int width, int height) {                    child.layout(left, top, left + width, top + height);      }      ...  }     






0 0