什么是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相关推荐

  1. Android学习笔记:Android基础知识点(不断更新中)

    1.Android学习笔记:OkHttp 2.Android学习笔记:更新UI的方法(UI线程和非UI线程) 3.Android学习笔记:Volley 4.Android学习笔记:Handler 5. ...

  2. Android学习笔记21:ImageView获取网络图片

    Android平台有3种网络接口可以使用,它们分别是:java.net.*(标准java接口).org.apache(Apache接口)和android.net.*(Android网络接口).本文将使 ...

  3. Android学习笔记(七):多个Activity和Intent

    根据www.mars-droid.com:Andriod开发视频教学,先跳过书本<Beginning Android 2>的几个章,我是这两个资源一起看,需要进行一下同步.先初步了解一下应 ...

  4. Android学习笔记26:图片切换控件ImageSwitcher的使用

    在Windows操作系统中,要查看多张图片,可以通过使用"Windows照片查看器"在"上一张"和"下一张"之间切换,进行多张图片的浏览. ...

  5. Android学习笔记36:使用SQLite方式存储数据

    在Android中一共提供了5种数据存储方式,分别为: (1)Files:通过FileInputStream和FileOutputStream对文件进行操作.具体使用方法可以参阅博文<Andro ...

  6. Pro Android学习笔记(二九):用户界面和控制(17):include和merge

    xml控件代码重用:include 如果我们定义一个控件,需要在不同的layout中重复使用,或者在同一个layout中重复使用,可以采用include的方式.例如定义my_button.xml如下 ...

  7. Android学习笔记:TabHost 和 FragmentTabHost

    2019独角兽企业重金招聘Python工程师标准>>> Android学习笔记:TabHost 和 FragmentTabHostTabHost命名空间:android.widget ...

  8. 【转】 Pro Android学习笔记(二九):用户界面和控制(17):include和merge

    目录(?)[-] xml控件代码重用include xml控件代码重用merge 横屏和竖屏landsacpe portrait xml控件代码重用:include 如果我们定义一个控件,需要在不同的 ...

  9. Android学习笔记:TabHost 和 FragmentTabHost(转)

    Android学习笔记:TabHost 和 FragmentTabHost(转) 转自:http://www.cnblogs.com/asion/p/3339313.html 作者:Asion Tan ...

  10. Pro Android学习笔记(三三):Menu(4):Alternative菜单

    什么是Alternative menu(替代菜单) 举个例子,Activity显示一个文本文件.如果用户想对文本文件进行编辑,Activity不提供编辑能力,但可由其他activity或者其他应用提供 ...

最新文章

  1. Hadoop集群的NameNode的备份
  2. 关于spring读取配置文件的两种方式
  3. 信息管理系统(Servlet+jsp+mvc+jdbc)
  4. IIS6.0+PHP+Mysql+Zend环境组建[图文]
  5. sap中Excel的模版上传和下载
  6. python pynlpir NLPIR许可证过期问题【RuntimeError: NLPIR function ‘NLPIR_Init‘ failed.】
  7. 时序分析:手势--空间轨迹模式识别
  8. 文档基本结构标签的作用
  9. Linux进程间通信的方法和示例
  10. sql 字段相减_R语言ETL系列:创建字段(mutate)
  11. 开源项目bootdo的实战开发笔记
  12. C# 图书管理系统(MySQL)——代码(四)
  13. 第二篇第五章防火防烟分区于分隔
  14. Python四种读取数据文件的方法
  15. 仿微信公众号界面实现
  16. 在android view中写坦克大战
  17. echart饼图标签重叠_解决echarts中饼图标签重叠的问题
  18. 网络信息安全领域中常见的几个概念
  19. MySQL倒序查询最后三条语句_MySQL 中 一条 order by index limit 语句的分析
  20. 国外服务器被攻击以及应对方法

热门文章

  1. 远程桌面连接下拉框IP地址删除
  2. 海康威视错误代码说明(六)(错误代码:82~825)
  3. 208个地级市和31个省市城乡泰尔指数(2010-2019年)
  4. linux越狱连接不了设备,Checkra1n 越狱常见问题汇总
  5. vue前端UI框架收集
  6. 批量调取接口_调用API接口批量查手机归属地
  7. 彻底卸载2345系列
  8. Geek(一个好用的强力卸载软件工具,包括注册表所有依赖项全部清理掉)
  9. 使用小丸工具箱进行极限视频压缩
  10. 人工智能 - NBA球星产生式系统