Android 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);
}
}
- .NET Core系列之MemoryCache 初识
- 007手机一键Root(安机网一键Root) v3.0 官方最新版 一键ROOT您的Android手机
- 12306密码被盗了怎么办?12306密码外泄解决方法
- 12个字的qq网名
- 150M迷你型无线路由器怎么设置?
- 192.168.1.1打不开怎么办?路由器192.168.1.1打不开的原因以及解决办法
- 2011年电子报合订本 电子报 编辑部 中文 PDF版 [84M]
- 2015年1月15日小米新旗舰发布会现场图文直播
- 2016.3.1vivo Xplay5新品发布会现场视频直播 优酷直播
- 2016华为P9发布会视频直播地址 4月15日华为P9国行发布会直播
相关文章
- Android获取联系人头像的方法
- Android入门之使用SQLite内嵌式数据库详解
- Android中使用TextureView播放视频
- 360隐私保险箱官方免费版for Android(隐私保护软件)v1.1.0.1013
- Android定时器实现的几种方式整理及removeCallbacks失效问题解决
- Android 使用Gallery实现3D相册(附效果图+Demo源码)
- Android入门之ViewFlipper翻转视图的使用详解
- ava云平台app(奥威亚直播教育平台) for Android v1.0.0 安卓版
- Android实现静默安装的两种方法
- Android实现关机与重启的几种方式(推荐)