理解 MeasureSpec

理解 MeasureSpec在开始本篇文章之前 我们先看一段代码 Overrideprot intwidthMeas intheightMea intexpendSpe MeasureSpec makeMeasureS Integer MAX VALUE gt gt 2 MeasureS

在开始本篇文章之前,我们先看一段代码:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expendSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expendSpec); }

是不是很熟悉,没错,它就是我们在 ScrollView 嵌套 ListView 的时候,重写 ListView 来处理 ListView 数据显示不全的问题,那么为什么要这么写呢,而 MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST) 又是什么意思呢,别着急,重头戏马上到来。

1. MeasureSpec 是干什么的

确切的说,MeasureSpec 在很大程度上决定了一个 View 的尺寸规格,之所以说很大程度上是因为这个过程还受父容器的影响,因为父容器影响 View 的 MeasureSpec 创建过程,在测量过程中,系统会将 View 的 LayoutParams 根据父容器所施加的规转换成对应的 MeasureSpec,然后再根据这个 MeasureSpec 来测量出 View 的宽高,上面提到过,这里的宽高是测量的宽高,不一定等于 View 的最终宽高,MeasureSpec 看起来有点复杂,其实他的实现还是很简单的,下面会详细的分析MeasureSpec 

2. MeasureSpec 的原理

理解 MeasureSpec



先来看一张图,带着问题来解读接下来要说的内容。

MeasureSpec 代表一个32为的 int 值,高两位是 SpecMode,低30位是 SpecSize,SpecMode 是指测量模式,而 SpecMode 是指在某种测量模式下的规格大小,下面先看下 MeasureSpec 内部的一些常量的定义,通过下面的代码,应该不难理解 MeasureSpec 的工作原理:

 private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; / @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface MeasureSpecMode {} / * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; / * 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. */ public static final int EXACTLY = 1 << MODE_SHIFT; / * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; / * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: * 
  
    *
  • {@link android.view.View.MeasureSpec#UNSPECIFIED}
  • *
  • {@link android.view.View.MeasureSpec#EXACTLY}
  • *
  • {@link android.view.View.MeasureSpec#AT_MOST}
  • *
* *

Note: On API level 17 and lower, makeMeasureSpec's * implementation was such that the order of arguments did not matter * and overflow in either value could impact the resulting MeasureSpec. * {@link android.widget.RelativeLayout} was affected by this bug. * Apps targeting API levels greater than 17 will get the fixed, more strict * behavior.

* * @param size the size of the measure specification * @param mode the mode of the measure specification * @return the measure specification based on size and mode */ public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } / * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED * will automatically get a size of 0. Older apps expect this. * * @hide internal use only for compatibility with system widgets and older apps */ public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } / * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } / * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }

MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包的方法。SpecMode 和 SpecSize 也是一个 int 值,一组 SpecMode 和 SpecSize 可以打包成一个 MeasureSpec,而一个 MeasureSpec 可以通过解包的方法来得出原始的 SpecMode 和 SpecSize,需要注意的是这里提到的 MeasureSpec 是值 MeasureSpec 所代表的 int 值,而并非 MeasureSpec 本身。

3. MeasureSpec 的三种模式

(1)UNSPECIFIEND

父容易不对 View 有任何影响,要多大给多大,这种情况一般用于系统内部,表示一种测量模式(我也不是太懂,有懂的大佬指点下)

(2)EXACTLY

父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 MeasureSpec 所指定的值,它对应于 LayoutParams 的 match_parent 和具体的数值两种模式。(如果是重写控件,慎用该模式,不然你会发现一件很神奇的事情,想知道的可以自己动手试试)

(3)AT_MOST

父容器指定一个大小即 SpecSize,View 的大小不能大于这个值,具体是什么值要看不同 View 的具体实现,它对应于 LayoutParams 的warp_content。

4.MeasureSpec 和 LayoutParams 对应关系

上面提到,系统内部是通过 MeasureSpec 来进行 View 的测量的,但是正常情况下我们使用 View 指定 MeasureSpec,尽管如此,但是我们给 View 设置 LayoutParams,在 View 测量的时候,系统会将 LayoutParams 在父容器的约束下转换成 MeasureSpec,然后在根据  MeasureSpec 来确定 View 测量后的宽高,需要注意的是 MeasureSpec 不是唯一有 LayoutParams 决定的,LayoutParams 需要和父容器一起才能决定 View 的 MeasureSpec,从而进一步决定 View 的宽高。另外,对于顶级 View(DecorView)和普通 View 来说,MeasureSpec 的转换过程略有不同,其 MeasureSpec 有窗口的尺寸和其自身的 LayoutParams 来共同决定;对于普通的 View,其 MeasureSpec 由父容器的 MeasureSpec 和 自身的 LayoutParams 决定,MeasureSpec 一旦确定后,onMeasure 中就可以确定 View 的测量宽高。
对于 DecorView 来说,在RootViewImpl 中的 measureHierarcly 方法中 有如下一段代码,它展示了 DecorView 的 MeasureSpec 的创建过程,其中 desiredWindowWidth 和 desiredWindowHeight 是屏幕的尺寸大小:

 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

接着再看一下 getRootMeasureSpec 方法实现:

/ * Figures out the measure spec for the root view in a window based on it's * layout params. * * @param windowSize * The available width or height of the window * * @param rootDimension * The layout params for one dimension (width or height) of the * window. * * @return The measure spec to use to measure the root view. */ 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 的 MeasureSpec 的产生过程就很明了了,具体来说遵守如下规则,根据它的 LayoutParams 中的宽高的参数来划分:

(1)LayoutParams.MATCH_PARENT:精确模式,大小就是窗口大小;
(2)LayoutParams.WARP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小
(3)固定大小(不如100DP):精确模式,大小为 LayoutParams 中指定的大小。
对于 View 来说,这里是指我们布局中的 View,View 的 measure 过程由 ViewGroup 传递过来,先看一下 ViewGroup 的 measureChildWithMargins2 方法:

 // Note: padding has already been removed from the supplied specs private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, int childWidth, int childHeight) { int childWidthSpec = getChildMeasureSpec(parentWidthSpec, getTotalMargin(child, true), childWidth); int childHeightSpec = getChildMeasureSpec(parentHeightSpec, getTotalMargin(child, false), childHeight); child.measure(childWidthSpec, childHeightSpec); }

上述方法会对子元素进行 measure,在调用子元素的 measure 方法之前会先调通过 getChildMeasureSpec 的创建与父容器的 MeasureSpec 和子元素本身的 LayoutParams 有关,此外还和 View 的 margins 及 padding 有关,具体情况可以看一下 ViewGroup 的 getChildMeasureSpec 方法,如下所示:

 / * Does the hard part of measureChildren: figuring out the MeasureSpec to * pass to a particular child. This method figures out the right MeasureSpec * for one dimension (height or width) of one child view. * * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and * margins, if applicable * @param childDimension How big the child wants to be in the current * dimension * @return a MeasureSpec integer for the child */ 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 us case 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 us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = 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 be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 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 be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

上述方法不难理解,它的主要作用是根据父容器的 MeasureSpec 同时结合 View 本身的 LayoutParams 来确定子元素的 MeasureSpec ,参数中的 padding 是指父容器中已经占用的空间大小,因此子元素可用的大小为父容器的尺寸减去 padding ,具体代码如下:

 int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding);

getChildMeasureSpec 清楚展示了普通 View 的 MeasureSpec 的创建规则。这个整个过程正好对应了文章开头那个表格,然后咱们再回来看下这个表格:
理解 MeasureSpec



针对上图,这里再做下说明,前面已经提到,对于普通的 View,其 MeasureSpec 有父容器的 MeasureSpec 和自身的 LayoutParams 来决定的,那么针对不同的父容器和 View 本身不同的 LayoutParams,View 就可以有多张 MeasureSpec。这里简单说一下,当 View 采用固定宽高的时候,不管父容器的 MeasureSpec 是什么,View 的 MeasureSpec 都是精确模式并且大小遵循 LayoutParams 中的大小,当 View 中的宽高是 match_parent 时,如果父容器的模式是精准模式,那么 View 也是精准模式,其大小是父容器的剩余空间,如果父容器是最大模式,那么 View 也是最大模式但大小不会超过父容器的剩余空间。当 View 的宽高是 warp_content 时,不管父容器的模式是精准还是最大模式, View 的模式总是最大模式但大小不会超过父容器的剩余空间。至于 UNSPECIFIEND 模式,我是真的不知道有啥用,等以后弄明白了再补充吧,或者哪位大神知道,可以说下,谢谢!!
通过上边的表格可以看出,我们知道了父容器的 MeasureSpec 和子元素的 LayoutParams,就可以快速地确定出子元素的 MeasureSpec 了,有了 MeasureSpec 就可以进一步确定出子元素测量后的大小了。



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/205691.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午5:26
下一篇 2026年3月19日 下午5:26


相关推荐

  • 小程序“成语猜题”部分答案,总共28667题

    小程序“成语猜题”部分答案,总共28667题哀哀父母 可哀呀可哀,我的父母啊!原指古时在暴政下人民终年在外服劳役,对父母病痛、老死不能照料而悲哀。 哀哀欲绝 绝:断气,死。形容极其悲痛。 哀兵必胜 原意是力量相当的两军对阵,悲愤的一方获得胜利。后指受欺侮而奋起抵抗的军队,必定能取胜。 哀而不伤 哀:悲哀;伤:伤害。忧愁而不悲伤。比喻做事没有过头也无不及。 哀感天地 形容极其哀痛,使天地都为之感动。 哀感顽艳 原意是指内容凄切,文辞华丽,使愚笨和聪慧的人同样受感动。后多用来指艳情的小说、戏曲、电影

    2022年6月5日
    53
  • python中的eval函数的用法_isnan函数

    python中的eval函数的用法_isnan函数eval函数在Python中具有非常重要的地位,熟练的使用eval函数能够为我们的Python编程提供很多的便利之处。在本文中我将详细记录eval函数在Python中的使用方法及它带来便利时带来的一些其他危害,希望您阅读完本文后能够有所收获。欢迎在文章下方留言共同交流学习。

    2025年8月12日
    4
  • Java HashSet的实现原理详解

    Java HashSet的实现原理详解HashSet是JavaMap类型的集合类中最常使用的,本文基于Java1.8,对于HashSet的实现原理做一下详细讲解。(Java1.8源码:http://docs.oracle.com/javase/8/docs/api/)一、HashSet实现原理总结HashSet的实现原理总结如下:①是基于HashMap实现的,默认构造函

    2025年7月3日
    4
  • 加密网站地址——从根本解决盗连

    加密网站地址——从根本解决盗连这是我在CSDN上的一篇远程文章《加密网站地址——从根本解决盗连》网站防止图片等内容盗链的方法多种多样,其中最常用的方法就是通过HTTP访问头信息,判断访问来源。理解TCP/IP通信员里的读者都知道,由于HTTP头信息处理是在OSI模型的应用层,所以,编造一些假的HTTP头信息发送给服务器,并不需要什么特殊的技术或者工具。下面的演示中,是利用wget命令模拟Fire…

    2022年6月13日
    39
  • 刷完 900 多道算法题的首次总结:LeetCode 应该怎么刷?「建议收藏」

    刷完 900 多道算法题的首次总结:LeetCode 应该怎么刷?「建议收藏」LeetCode算法题应该怎么刷?算法题学习路线?怎么组队刷题?有没有算法刷题群?你是怎么入门LeetCode的?刷完1000道算法题是什么体验?大家都是怎么刷题的?新手小白开始刷算法题应该怎么刷,注意什么?刷LeetCode总是觉得太难怎么办?刷算法题有没有书籍推荐?该按照什么顺序刷算法题?LeetCode怎么看别人的题解?有哪些好的算法题博主推荐?

    2022年6月17日
    27
  • java 大学考试_java考试 (全部)「建议收藏」

    java 大学考试_java考试 (全部)「建议收藏」该楼层疑似违规已被系统折叠隐藏此楼查看此楼importjava.awt*;importjava.awtevent*;classABCimplementsActionListener,WindowListener{Buttonb1;Buttonb2;TextPieldshowing;publicvoidactionperformed(ActionEvente){if(e…

    2022年7月7日
    21

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号