Android学习笔记之MeasureSpec
什么是MeasureSpec
Android系统在绘制View的时候,过程是十分复杂的,其中频繁的使用到了MeasureSpec。那么MeasureSpec是什么?有什么用?简单点说,它是一个int值的中间变量,用来存储View的尺寸规格。再说细点,在测量过程中,系统会将View的LayoutParams根据父容器所施加的约束规则转换成对应的MeasureSpec。
MeasureSpec代表一个32位int值,高两位代表SpecMode,低30位代表SpecSize,SpecMode是指测试模式,而SpecSize是指在某中测试模式下的规格大小。源码如下:
public static class MeasureSpec {private static final int MODE_SHIFT = 30;//高两位11private static final int MODE_MASK = 0x3 << MODE_SHIFT;//高两位00public static final int UNSPECIFIED = 0 << MODE_SHIFT;//高两位01public static final int EXACTLY = 1 << MODE_SHIFT;//高两位10public static final int AT_MOST = 2 << MODE_SHIFT;public static int makeMeasureSpec(int size, int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size & ~MODE_MASK) | (mode & MODE_MASK);}}public static int makeSafeMeasureSpec(int size, int mode) {if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {return 0;}return makeMeasureSpec(size, mode);}@MeasureSpecModepublic static int getMode(int measureSpec) {//取高2位得模式return (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) {//取低30位得规格大小return (measureSpec & ~MODE_MASK);}
}
什么是约束规则
上面解释MeasureSpec时,提及到父容器的约束规则,可能会让我们联想到 match_parent 和 wrap_content,但这里说的约束规则并不完全是这样。具体来说,约束规则指的是MeasureSpec中的三种模式,即SpecMode,它是通过与 match_parent 和 wrap_content 相关的逻辑判断最后决定下来的。每一种模式的含义如下:
- UNSPECIFIED:父容器不对View有任何限制,要多大给多大,一般用于系统内部,表示一种测试状态。
- EXACTLY:表示View的规格为确切值,即SpecSize所指定的值。它对应于match_parent和具体的数值这两种模式。
- AT_MOST:表示View的规格不能超过某个值,具体是什么值要看不同View的具体实现。它对应于wrap_content。
MeasureSpec和LayoutParams的关系
上面提到, 在测量过程中,系统会将View的LayoutParams根据父容器所施加的约束规则转换成对应的MeasureSpec。这里需要注意的是,对于顶级View(DecorView)来说,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通View来说,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。
对DecorView来说,创建MeasureSpec的源码如下:(其中desireWindowWidth和desireWindowHeight是屏幕的尺寸)
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
再看一下getRootMeasureSpec方法
private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;
}
通过上述代码可以得出以下结论:
- 如果DecorView的LayoutParams中的宽/高参数为match_parent,那么它的MeasureSpec的模式为精确模式,尺寸为窗口大小
- 如果DecorView的LayoutParams中的宽/高参数为wrap_content,那么它的MeasureSpec的模式为最大模式,最大尺寸为窗口大小
- 如果DecorView的LayoutParams中的宽/高参数为具体值(例如100dp),那么它的MeasureSpec的模式为精确模式,尺寸为指定的具体值(100dp)
对普通View来说,View的measure过程是由它的父容器ViewGroup调用的,如下:
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,父容器获取子元素的布局参数之后,通过getChildMeasureSpec方法获取子元素的宽高MeasureSpec,然后调用子元素的measure方法。细心点会发现,这里的measureChild方法并没有考虑到子元素Margin属性值,实际上ViewGroup中还有一个遍历子元素measure过程的方法,是有考虑Margin属性的,如下:
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();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);
}
具体思路是一样的,只是在传参的时候考虑了子元素的Margin属性。他们都会在最后调用子元素的measure方法,开始子元素的measure过程。接着我们来看getChildMeasureSpec方法的源码:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.resultSize = size;resultMode = MeasureSpec.EXACTLY;} 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;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} 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;} 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;// Parent asked to see how big we want to becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how// big it should beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上述代码不难理解,总的思路是按照父容器的SpecMode分为三个分支,然后在每个分之中结合子元素本身的LayoutParams来确定子元素的MeasureSpec。通过上述代码可以得出以下结论:
- 当子View采用固定宽/高时,不管父容器的MeasureSpec是什么模式,View的MeasureSpec都是精确模式并且大小为LayoutParams中指定的具体值
- 当子View的宽/高是 match_parent 时,如果父容器的模式是精确模式,那么子View也是精确模式并且大小是父容器的剩余空间;如果父容器的模式是最大模式,那么子View也是最大模式并且大小不会超过父容器的剩余空间
- 当子View的宽/高是 wrap_content 时,不管父容器是最大模式还是精确模式,子View的模式总是最大模式并且大小不会超过父容器的剩余空间
PS:上面的总结没有考虑UNSPECIFIED模式,是因为该模式主要用于系统内部多次Measure的情形,通常情况下我们不需要关注它。
Android学习笔记之MeasureSpec相关推荐
- Android学习笔记:Android基础知识点(不断更新中)
1.Android学习笔记:OkHttp 2.Android学习笔记:更新UI的方法(UI线程和非UI线程) 3.Android学习笔记:Volley 4.Android学习笔记:Handler 5. ...
- Android学习笔记21:ImageView获取网络图片
Android平台有3种网络接口可以使用,它们分别是:java.net.*(标准java接口).org.apache(Apache接口)和android.net.*(Android网络接口).本文将使 ...
- Android学习笔记(七):多个Activity和Intent
根据www.mars-droid.com:Andriod开发视频教学,先跳过书本<Beginning Android 2>的几个章,我是这两个资源一起看,需要进行一下同步.先初步了解一下应 ...
- Android学习笔记26:图片切换控件ImageSwitcher的使用
在Windows操作系统中,要查看多张图片,可以通过使用"Windows照片查看器"在"上一张"和"下一张"之间切换,进行多张图片的浏览. ...
- Android学习笔记36:使用SQLite方式存储数据
在Android中一共提供了5种数据存储方式,分别为: (1)Files:通过FileInputStream和FileOutputStream对文件进行操作.具体使用方法可以参阅博文<Andro ...
- Pro Android学习笔记(二九):用户界面和控制(17):include和merge
xml控件代码重用:include 如果我们定义一个控件,需要在不同的layout中重复使用,或者在同一个layout中重复使用,可以采用include的方式.例如定义my_button.xml如下 ...
- Android学习笔记:TabHost 和 FragmentTabHost
2019独角兽企业重金招聘Python工程师标准>>> Android学习笔记:TabHost 和 FragmentTabHostTabHost命名空间:android.widget ...
- 【转】 Pro Android学习笔记(二九):用户界面和控制(17):include和merge
目录(?)[-] xml控件代码重用include xml控件代码重用merge 横屏和竖屏landsacpe portrait xml控件代码重用:include 如果我们定义一个控件,需要在不同的 ...
- Android学习笔记:TabHost 和 FragmentTabHost(转)
Android学习笔记:TabHost 和 FragmentTabHost(转) 转自:http://www.cnblogs.com/asion/p/3339313.html 作者:Asion Tan ...
- Pro Android学习笔记(三三):Menu(4):Alternative菜单
什么是Alternative menu(替代菜单) 举个例子,Activity显示一个文本文件.如果用户想对文本文件进行编辑,Activity不提供编辑能力,但可由其他activity或者其他应用提供 ...
最新文章
- Hadoop集群的NameNode的备份
- 关于spring读取配置文件的两种方式
- 信息管理系统(Servlet+jsp+mvc+jdbc)
- IIS6.0+PHP+Mysql+Zend环境组建[图文]
- sap中Excel的模版上传和下载
- python pynlpir NLPIR许可证过期问题【RuntimeError: NLPIR function ‘NLPIR_Init‘ failed.】
- 时序分析:手势--空间轨迹模式识别
- 文档基本结构标签的作用
- Linux进程间通信的方法和示例
- sql 字段相减_R语言ETL系列:创建字段(mutate)
- 开源项目bootdo的实战开发笔记
- C# 图书管理系统(MySQL)——代码(四)
- 第二篇第五章防火防烟分区于分隔
- Python四种读取数据文件的方法
- 仿微信公众号界面实现
- 在android view中写坦克大战
- echart饼图标签重叠_解决echarts中饼图标签重叠的问题
- 网络信息安全领域中常见的几个概念
- MySQL倒序查询最后三条语句_MySQL 中 一条 order by index limit 语句的分析
- 国外服务器被攻击以及应对方法