且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

MeasureSpec的理解和详尽源码分析

更新时间:2022-05-25 17:08:48

package cc.ww;

import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.LinearLayout;

/**
 * @author http://blog.csdn.net/lfdfhl
 * 
 * 文档描述:
 * 关于MeasureSpec的理解
 * 
 * (1) MeasureSpec基础知识
 *     MeasureSpec通常翻译为"测量规格",它是一个32位的int数据.
 *     其中高2位代表SpecMode即某种测量模式,低32位为SpecSize代表在该模式下的规格大小.
 *     可以通过:  
 *     int specMode = MeasureSpec.getMode(measureSpec) 获取specMode
	   int specSize = MeasureSpec.getSize(measureSpec) 获取SpecSize
	   
	       常用的SpecMode有三种:
	       
	   MeasureSpec.EXACTLY
	       官方文档
	   Measure specification mode: The parent has determined an exact size
       for the child. The child is going to be given those bounds regardless of how big it wants to be.
                  父容器已经检测出子View所需要的精确大小.该子View最终的测量大小即为SpecSize.
       (1) 当子View的LayoutParams的宽(高)采用具体的值(如100px)时且父容器的MeasureSpec为 MeasureSpec.EXACTLY或者
       MeasureSpec.AT_MOST或者MeasureSpec.UNSPECIFIED时:
                  系统返回给该子View的specMode就为 MeasureSpec.EXACTLY
                  系统返回给该子View的specSize就为子View自己指定的大小(childSize)
                  通俗地理解:
                  子View的LayoutParams的宽(高)采用具体的值(如100px)时,那么说明该子View的大小是非常明确的,明确到已经用具体px值
                  指定的地步了.那么此时不管父容器的specMode是什么,系统返回给该子View的specMode总是MeasureSpec.EXACTLY,并且
                  系统返回给该子View的specSize就为子View自己指定的大小(childSize).
       
       (2) 当子View的LayoutParams的宽(高)采用match_parent时并且父容器的MeasureSpec为 MeasureSpec.EXACTLY时:
                  系统返回给该子View的specMode就为 MeasureSpec.EXACTLY
                  系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
                  通俗地理解:
	       子View的LayoutParams的宽(高)采用match_parent时并且父容器的MeasureSpec为 MeasureSpec.EXACTLY.
	       这时候说明子View的大小还是挺明确的:就是要和父容器一样大,更加直白地说就是父容器要怎样子View就要怎样.
	       所以,如果父容器MeasureSpec为 MeasureSpec.EXACTLY那么:
	       系统返回给该子View的specMode就为 MeasureSpec.EXACTLY,和父容器一样.
                 系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize),就是父容器的剩余大小.
                  同样的道理如果此时,MeasureSpec为 MeasureSpec.AT_MOST呢?
                 系统返回给该子View的specMode也为 MeasureSpec.AT_MOST,和父容器一样.
                 系统返回给该子View的specSize也为该父容器剩余空间的大小(parentLeftSize),就是父容器的剩余大小.
	      
	   MeasureSpec.AT_MOST
	       官方文档
	   The child can be as large as it wants up to the specified size.
	        父容器指定了一个可用大小即specSize,子View的大小不能超过该值.
	   (1) 当子View的LayoutParams的宽(高)采用match_parent时并且父容器的MeasureSpec为 MeasureSpec.AT_MOST时:
                  系统返回给该子View的specMode就为 MeasureSpec.AT_MOST
                  系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
                  这种情况已经在上面介绍 MeasureSpec.EXACTLY时已经讨论过了.
      (2) 当子View的LayoutParams的宽(高)采用wrap_content时并且父容器的MeasureSpec为 MeasureSpec.EXACTLY时:
                  系统返回给该子View的specMode就为 MeasureSpec.AT_MOST
                  系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
                  通俗地理解:
                  子View的LayoutParams的宽(高)采用wrap_content时说明这个子View的宽高不明确,要视content而定.
                  这个时候如果父容器的MeasureSpec为 MeasureSpec.EXACTLY即父容器是一个精确模式;这个时候简单地说
                  子View是不确定的,父容器是确定的,那么
                  系统返回给该子View的specMode也就是不确定的即为 MeasureSpec.AT_MOST
                  系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
       (3) 当子View的LayoutParams的宽(高)采用wrap_content时并且父容器的MeasureSpec为 MeasureSpec.AT_MOST时:
                  系统返回给该子View的specMode就为 MeasureSpec.AT_MOST
                  系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
                  通俗地理解:
                  子View的LayoutParams的宽(高)采用wrap_content时说明这个子View的宽高不明确,要视content而定.
                  这个时候如果父容器的MeasureSpec为 MeasureSpec.AT_MOST这个时候简单地说
                  子View是不确定的,父容器也是不确定的,那么
                  系统返回给该子View的specMode也就是不确定的即为 MeasureSpec.AT_MOST
                  系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
                 
	   
	   MeasureSpec.UNSPECIFIED
	       官方文档
	   The parent has not imposed any constraint on the child. It can be whatever size it wants.
	       父容器不对子View的大小做限制.
	       一般用作Android系统内部,或者ListView和ScrollView.在此不做讨论.
	       
	       关于这个三种测量规格下面的源码分析中体现得很明显,也可参考以下附图.
	   
 * (2) 在onMeasure()时子View的MeasureSpec的形成过程分析
 *     关于该技术点的讨论,请看下面的源码分析.
 *
 */
public class UnderstandMeasureSpec {
	
	/**
	 * 第一步:
	 * 在ViewGroup测量子View时会调用到measureChildWithMargins()方法,或者与之类似的方法.
	 * 请注意方法的参数:
	 * @param child
	 * 子View
	 * @param parentWidthMeasureSpec
	 * 父容器(比如LinearLayout)的宽的MeasureSpec
	 * @param widthUsed
	 * 父容器(比如LinearLayout)在水平方向已经占用的空间大小
	 * @param parentHeightMeasureSpec
	 * 父容器(比如LinearLayout)的高的MeasureSpec
	 * @param heightUsed
	 * 父容器(比如LinearLayout)在垂直方向已经占用的空间大小
	 * 
	 * 在该方法中主要有四步操作,其中很重要的是调用了getChildMeasureSpec()方法来确定
	 * 子View的MeasureSpec.详情参见代码分析
	 */
	protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,
			                                          int parentHeightMeasureSpec, int heightUsed) {
		//1 得到子View的LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //2 得到子View的宽的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec
        (parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
        //3 得到子View的高的MeasureSpec
        final int childHeightMeasureSpec = getChildMeasureSpec
        (parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
        //4 测量子View
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
	
	
	/**
	 * getChildMeasureSpec()方法确定子View的MeasureSpec
	 * 请注意方法的参数:
	 * @param spec
	 * 父容器(比如LinearLayout)的宽或高的MeasureSpec
	 * @param padding
	 * 父容器(比如LinearLayout)在垂直方向或者水平方向已被占用的空间.
	 * 在measureChildWithMargins()方法里调用getChildMeasureSpec()时注意第二个参数的构成:
	 * 比如:mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
	 * 其中:
	 * mPaddingLeft和mPaddingRight表示父容器左右两内侧的padding
	 * lp.leftMargin和lp.rightMargin表示子View左右两外侧的margin
	 * 这四部分都不可以再利用起来布局子View.所以说这些值的和表示:
	 * 父容器在水平方向已经被占用的空间
	 * 同理:
	 * mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
	 * 表示:
	 * 父容器(比如LinearLayout)在垂直方向已被占用的空间.
	 * @param childDimension
	 * 通过子View的LayoutParams获取到的子View的宽或高
	 * 
	 * 
	 * 经过以上分析可从getChildMeasureSpec()方法的第一个参数和第二个参数可以得出一个结论:
	 * 父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同决定了子View的MeasureSpec!!!
	 * 
	 * 
	 * 
	 */
	 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
		    /**
		     * 第一步:得到父容器的specMode和specSize
		     */
	        int specMode = MeasureSpec.getMode(spec);
	        int specSize = MeasureSpec.getSize(spec);
	        /**
	         * 第二步:得到父容器在水平方向或垂直方向可用的最大空间值.
	         *        关于padding参见上面的分析
	         */
	        int size = Math.max(0, specSize - padding);

	        int resultSize = 0;
	        int resultMode = 0;

	        
	        /**
	         * 第三步:确定子View的specMode和specSize.
	         *        在此分为三种情况进行.
	         */
	        switch (specMode) {
	        /**
	         * 第一种情况:
	         * 父容器的测量模式为EXACTLY
	         * 
	         * 请注意两个系统常量:
	         * LayoutParams.MATCH_PARENT=-1
	         * LayoutParams.WRAP_CONTENT=-2
	         * 所以在此处的代码:
	         * childDimension >= 0 表示子View的宽或高不是MATCH_PARENT和WRAP_CONTENT
	         */
	        case MeasureSpec.EXACTLY:
	        	/**
	        	 * 当父容器的测量模式为EXACTLY时如果:
	        	 * 子View的宽或高是一个精确的值,比如100px;
	        	 * 那么:
	        	 * 子View的size就是childDimension
	        	 * 子View的mode也为MeasureSpec.EXACTLY
	        	 */
	            if (childDimension >= 0) {
	                resultSize = childDimension;
	                resultMode = MeasureSpec.EXACTLY;
	            /**
		         * 当父容器的测量模式为EXACTLY时如果:
		         * 子View的宽或高是LayoutParams.MATCH_PARENT
		         * 那么:
		         * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
		         * 子View的mode也为MeasureSpec.EXACTLY
		         */
	            } else if (childDimension == LayoutParams.MATCH_PARENT) {
	                // Child wants to be our size. So be it.
	                resultSize = size;
	                resultMode = MeasureSpec.EXACTLY;
	            /**
			     * 当父容器的测量模式为EXACTLY时如果:
			     * 子View的宽或高是LayoutParams.WRAP_CONTENT
			     * 那么:
			     * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
			     * 子View的mode为MeasureSpec.AT_MOST
			     */
	            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
	                // Child wants to determine its own size. It can't be bigger than us.
	                resultSize = size;
	                resultMode = MeasureSpec.AT_MOST;
	            }
	            break;

	        /**
		     * 第二种情况:
		     * 父容器的测量模式为AT_MOST
		     * 
		     * 请注意两个系统常量:pp
		     * LayoutParams.MATCH_PARENT=-1
		     * LayoutParams.WRAP_CONTENT=-2
		     * 所以在此处的代码:
		     * childDimension >= 0 表示子View的宽或高不是MATCH_PARENT和WRAP_CONTENT
		     */
	        case MeasureSpec.AT_MOST:
	        	/**
	        	 * 当父容器的测量模式为AT_MOST时如果:
	        	 * 子View的宽或高是一个精确的值,比如100px;
	        	 * 那么:
	        	 * 子View的size就是childDimension
	        	 * 子View的mode也为MeasureSpec.EXACTLY
	        	 */
	            if (childDimension >= 0) {
	                // Child wants a specific size... so be it
	                resultSize = childDimension;
	                resultMode = MeasureSpec.EXACTLY;
	            /**
		          * 当父容器的测量模式为AT_MOST时如果:
		          * 子View的宽或高为LayoutParams.MATCH_PARENT
		          * 那么:
		          * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
		          * 子View的mode也为MeasureSpec.AT_MOST
		          */
	            } 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;
	                resultMode = MeasureSpec.AT_MOST;
	             /**
			      * 当父容器的测量模式为AT_MOST时如果:
			      * 子View的宽或高为LayoutParams.WRAP_CONTENT
			      * 那么:
			      * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
			      * 子View的mode也为MeasureSpec.AT_MOST
			      */
	            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
	                // Child wants to determine its own size. It can't be
	                // bigger than us.
	                resultSize = size;
	                resultMode = MeasureSpec.AT_MOST;
	            }
	            break;

	        /**
			  * 第三种情况:
			  * 父容器的测量模式为UNSPECIFIED
			  * 
			  * 请注意两个系统常量:
			  * LayoutParams.MATCH_PARENT=-1
			  * LayoutParams.WRAP_CONTENT=-2
			  * 所以在此处的代码:
			  * childDimension >= 0 表示子View的宽或高不是MATCH_PARENT和WRAP_CONTENT
			  */
	        case MeasureSpec.UNSPECIFIED:
	        	/**
	        	 * 当父容器的测量模式为UNSPECIFIED时如果:
	        	 * 子View的宽或高是一个精确的值,比如100px;
	        	 * 那么:
	        	 * 子View的size就是childDimension
	        	 * 子View的mode也为MeasureSpec.EXACTLY
	        	 */
	            if (childDimension >= 0) {
	                // Child wants a specific size... let him have it
	                resultSize = childDimension;
	                resultMode = MeasureSpec.EXACTLY;
	            /**
			     * 当父容器的测量模式为UNSPECIFIED时如果:
			     * 子View的宽或高为LayoutParams.MATCH_PARENT
			     * 那么:
			     * 子View的size为0
			     * 子View的mode也为MeasureSpec.UNSPECIFIED
			     */
	            } else if (childDimension == LayoutParams.MATCH_PARENT) {
	                // Child wants to be our size... find out how big it should be
	                resultSize = 0;
	                resultMode = MeasureSpec.UNSPECIFIED;
	            /**
				 * 当父容器的测量模式为UNSPECIFIED时如果:
			     * 子View的宽或高为LayoutParams.WRAP_CONTENT
				 * 那么:
				 * 子View的size为0
				 * 子View的mode也为MeasureSpec.UNSPECIFIED
				 */
	            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
	                // Child wants to determine its own size.... find out how big it should be
	                resultSize = 0;
	                resultMode = MeasureSpec.UNSPECIFIED;
	            }
	            break;
	        }
	        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
	    }
	
	

}



MeasureSpec的理解和详尽源码分析