Android  MeasureSpec的理解和源码的解析

时间:2022-07-28
本文章向大家介绍Android  MeasureSpec的理解和源码的解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Android MeasureSpec的理解和源码的解析

MeasureSpec的创建规则:

实例详解:

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); 
} 
} 

如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!