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

转载出处: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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值