十八 、 View 的工作原理(2)---理解 MeasureSpec
MeasureSpec 是什么:
它是 Android 源码中 View.java 中的一个静态内部类:
public static class MeasureSpec {private static final int MODE_SHIFT = 30;private static final int MODE_MASK = 0x3 << MODE_SHIFT;public static final int UNSPECIFIED = 0 << MODE_SHIFT;public static final int EXACTLY = 1 << MODE_SHIFT;public 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 getMode(int measureSpec) {return (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);}
}
从源码中可以看到它的内部有一个静态方法 makeMeasureSpec() ,通过它可以创建一个 MeasureSpec 值,返回值为 int 类型,所以实际上 MeasureSpec 代表一个 32 位的 int 值。通过 getMode() 和 getSize() 方法可以看到它被分为两部分,分别为高 2 位为 SpecMode,低 30 位代表 SpecSize。SpecMode 是指测量模式,SpecSize 是指在某种测量模式下的规格大小,可以分别通过 getMode() 和 getSize() 方法获取。通过上面 Android 源码提供的方法我们可以看到,通过 View 的 MeasureSpec,我们可以使用 getMode() 和 getSize() 分别获取到它的 SpecMode 和 SpecSize;通过View 的 SpecMode 和 SpecSize,我们可以使用 makeMeasureSpec() 方法获取到它的 MeasureSpec 。
注意,上面所提到的 MeasureSpec 是指 MeasureSpec 所代表的 int 值,而并非 MeasureSpec 本身,它本身是 View 类中的一个静态内部类。
上面说了 SpecMode 是指测量模式,在 Android 系统中,它定义了三种情况:
UNSPECIFIED:(未指定模式)父容器不对 View 有任何限制,要多大给多大,这种情况一般用于系统内部。
EXACTLY:( /ɪg'zæk(t)lɪ/ 精确模式)父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值这两种模式。
AT_MOST:(最大模式)父容器指定了一个可用大小即 SpecSize,View 的最终大小不能大于这个值,具体是什么值要看不同 View 的具体实现。它对应于 LayoutParams 中的 wrap_content。
MeasureSpec 的作用:
它在 View 的测量过程中起作用。在 View 的 measure 方法中,需要 View 的 MeasureSpec 作为参数传递进去。Android 系统会根据 View 的 LayoutParams 以及父容器的 MeasureSpec 转换成对应的该 View 的 MeasureSpec,然后再根据这个 MeasureSpec 的值执行 measure() 方法,最后测量出 View 的宽/高。所以在很大程度上它决定了一个 View 的尺寸规格,之所以说很大程度上,是因为子 View 的 MeasureSpec 是由父容器的 MeasureSpec 和它本身的 LayoutParams 共同决定的,而不仅仅是由子 View 的 LayoutParams 来决定的。
MeasureSpec 和 LayoutParams 的关系以及 MeasureSpec 转换过程:
在 View 测量的时候,系统会将 View 的 LayoutParams 在父容器的约束下转换成对应的 MeasureSpec ,然后再根据这个 MeasureSpec 来确定 View 测量后的宽/高。所以,MeasureSpec 并不仅仅是由 View 的 LayoutParams 来决定的,LayoutParams 需要和父容器一起才能决定 View 的 MeasureSpec,从而进一步决定 View 的宽/高。
这里需要注意,对于顶层 View,也就是 DecorView 和普通 View ,它们的 MeasureSpec 转换过程略有不同:
DecorView 的 MeasureSpec 转换过程:由窗口的尺寸和其自身的 LayoutParams 来共同决定的。
普通 View 的 MeasureSpec 转换过程:由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定的。
MeasureSpec 一旦确定后,onMeasure() 方法中就可以确定 View 的测量宽/高,下面通过源码来具体分析一下 MeasureSpec 的转换过程:
DecorView 的 MeasureSpec 转换过程:
1. 对于 DecorView 来说,在 ViewRootImpl 中 的 measureHierarchy() 方法中开始它的测量过程:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {int childWidthMeasureSpec;int childHeightMeasureSpec;boolean goodMeasure = false;...if (!goodMeasure) {// desiredWindowWidth 和 desiredWindowHeight 是窗口的尺寸,lp 类型为 LayoutParams。childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}...return windowSizeMayChange;
}
下面看一下 getRootMeasureSpec() 方法内部转换过程:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;// 根据 DecorView 的 LayoutParams 中的宽/高参数进行分支switch (rootDimension) {// 当宽/高设置为 match_parent 时,SpecMode 为 EXACTLY 模式case ViewGroup.LayoutParams.MATCH_PARENT:// 精确模式,大小就是 windowSize 的大小,也就是窗口的大小。measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;// 当宽/高设置为 wrap_content 时,SpecMode 为 AT_MOST 模式case ViewGroup.LayoutParams.WRAP_CONTENT:// 最大模式,大小是窗口的大小。measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// 默认为精确模式,大小为 DecorView 的 LayoutParams 中的宽/高measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;
}
普通 View 的 MeasureSpec 转换过程:
1. 对于普通 View 来说,这里的普通 View 指的是我们布局中的 View,它的 measure 过程是由它的父布局 (ViewGroup) 传递而来的,所以我们从 ViewGroup 中 measure 的传递之前开始分析,它开始往子 View 传递的方法是在 measureChildWithMargins() 中:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {// 获取子 View 的 LayoutParamsfinal MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// 这里就是具体获取 View 的 MeasureSpec 方法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);}
2. 可以看到,先通过 getChildMeasureSpec() 方法获取子 View 的 MeasureSpec,然后再根据子 View 的 MeasureSpec 执行子 View 的 measure 过程。所以接下来看看 getChildMeasureSpec():
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {// 通过父容器的 MeasureSpec 获取其 SpecMode 和 SpecSizeint specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);/** padding = 父容器的 mPaddingLeft + 父容器的 mPaddingRight + 子 View LayoutParams的 leftMargin + 子 View LayoutParams的 rightMargin + 父容器已经使用的宽度 widthUsed* size = 父容器的总大小 - 父容器中已占用的空间大小* */int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;// 根据父容器中 MeasureSpec 中的 SpecMode 进行分支switch (specMode) {// 当父容器 SpecMode 为精确模式时case MeasureSpec.EXACTLY:// 再根据子 View 的 LayoutParams 进行判断if (childDimension >= 0) {// 当子 View 的 LayoutParams 为精确大小时resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 当子 View 的 LayoutParams 为 match_parent 时resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 当子 View 的 LayoutParams 为 wrap_content 时resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// 当父容器 SpecMode 为最大模式时case MeasureSpec.AT_MOST:if (childDimension >= 0) {// 当子 View 的 LayoutParams 为精确大小时resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 当子 View 的 LayoutParams 为 match_parent 时resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 当子 View 的 LayoutParams 为 wrap_content 时resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// 当父容器 SpecMode 为 UNSPECIFIED 模式时case MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// 当子 View 的 LayoutParams 为精确大小时resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 当子 View 的 LayoutParams 为 match_parent 时resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 当子 View 的 LayoutParams 为 wrap_content 时resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}// 返回子 View 的 MeasureSpecreturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
综上,可以看到普通 View 的 MeasureSpec 创建过程需要父容器的 MeasureSpec 以及该 View 本身的 LayoutParams 来共同决定的。
普通 View 的 MeasureSpec 创建总结图表:
十八 、 View 的工作原理(2)---理解 MeasureSpec相关推荐
- 《Android开发艺术探索》读书笔记 (4) 第4章 View的工作原理
本节和<Android群英传>中的第3章Android控件架构与自定义控件详解有关系,建议先阅读该章的总结 第4章 View的工作原理 4.1 初始ViewRoot和DecorView ( ...
- 深入解析Android中View的工作原理
Android中的任何一个布局.任何一个控件其实都是直接或间接继承自View实现的,当然也包括我们在平时开发中所写的各种炫酷的自定义控件了,所以学习View的工作原理对于我们来说显得格外重要,本篇博客 ...
- uart怎么判断帧错误_UART通讯总线工作原理的理解
奥的斯电梯OCSS/LCBII /TCBC/GECB板与电梯轿厢和电梯井道之间的串行通讯采用了UART通讯,将井道和轿厢的输入.输出和开关部件的信号转换成串行通讯信号传输给电梯操作控制系统,大大节省了 ...
- uart怎么判断帧错误_UART通讯总线工作原理的理解--龚玉山
奥的斯电梯OCSS/LCBII /TCBC/GECB板与电梯轿厢和电梯井道之间的串行通讯采用了UART通讯,将井道和轿厢的输入.输出和开关部件的信号转换成串行通讯信号传输给电梯操作控制系统,大大节省了 ...
- Android学习笔记View的工作原理
自定义View,也可以称为自定义控件,通过自定义View可以使得控件实现各种定制的效果. 实现自定义View,需要掌握View的底层工作原理,比如View的测量过程.布局流程以及绘制流程,除此之外,还 ...
- 第十四节 OSAL工作原理
第十四节 OSAL工作原理 蓝牙为了实现同多个设备相连,或实现多功能,也实现了功能扩充,这就产生了调度问题.因为,虽然软件和协议栈可扩充,但终究最底层的执行部门只有一个.为了实现多事件和多任 ...
- 【学习】Android中View的工作原理(上)——ViewRoot、DecorView、MeasureSpec
初识ViewRoot和DecorView ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot完成 ...
- Spring aop 原始的工作原理的理解
理解完aop的名词解释,继续学习spring aop的工作原理. 首先明确aop到底是什么东西?又如何不违单一原则并实现交叉处理呢? 如果对它的认识只停留在面向切面编程,那就脏了.从oop(Objec ...
- 孔板流量计计算公式_十二种流量计工作原理
流量计根据工作原理有很多种,选型时需要根据实际工况来选择随适合的流量计.下面,就为大家汇总了各种流量计的工作原理,希望能对大家有所帮助. 1 差压式流量计 差压式流量计是根据安装于管道中流量检测件产生 ...
- android进阶(四)-----View的工作原理
前言: 好久没有发博客了,一直加班加到吐血,也是没谁了,最近也是互联网寒冬期,各大厂也都在裁员,提高自己才是正道啊. 一.ViewRoot和DecorView ViewRoot对对应于ViewRoot ...
最新文章
- 干货丨计算机视觉必读:图像分类、定位、检测,语义分割和实例分割方法梳理(经典长文,值得收藏)
- iPhone12机型判断
- CSU 1337 搞笑版费马大定理(2013湖南省程序设计竞赛J题)
- shp设置utf8格式_shp文件格式说明
- python网络爬虫实战 吕文翔_实战Python网络爬虫
- 拷贝 var lib mysql 备份_mysql复制与备份
- 所有铣床行业调研报告 - 市场现状分析与发展前景预测
- springcloud(十):服务网关zuul
- css3文档手册chm_你还在使用CHM帮助文档吗?赶快试试Baklib吧
- Dynamics AX2012 根据表字段查找对应的字段值
- 搜索、推荐、广告系统等人工智能优质技术资源最全整理
- php递归还原,php递归算法处理
- 叉积 微分 恒等式_单摆-微分方程浅谈
- 毕业设计:实现电脑识别魔方颜色
- android 九宫格手势密码 纯代码实现
- itext设置字体间距_word打印技巧:几个节省纸张的打印设置方法
- 建造者模式(Builder)---创建型
- 我的世界android启动器,我的世界手机版启动器 安卓方块启动器教程
- 网络七层协议地图,报文格式一览无遗。绝对是干货,值得收藏
- 支持国产ARM64架构部署,支持使用rz、sz命令上传下载文件,JumpServer堡垒机v2.12.0发布