您现在的位置是:亿华云 > 应用开发
温故而知新 MeasureSpec在View测量中的作用
亿华云2025-10-09 04:01:35【应用开发】6人已围观
简介前言对于MeasureSpec,你的认识有多少呢?MeasureSpec是干嘛的?存在的意义在哪? MeasureSpec中的mode和size到底指的是什么? Measu
前言
对于MeasureSpec,温故你的而知认识有多少呢?
MeasureSpec是干嘛的?存在的意义在哪? MeasureSpec中的mode和size到底指的是什么? MeasureSpec是怎么计算的,与哪些因素有关?测量 父View测量好子View的MeasureSpec之后,子View会怎么处理?中的作用 View/ViewGroup、DecorView的温故MeasureSpec有什么区别? UNSPECIFIED这个特殊模式又有什么用呢?介绍
首先,我们看下这个类:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; //00后面跟30个0 public static final int UNSPECIFIED = 0 << MODE_SHIFT; //01后面跟30个0 public static final int EXACTLY = 1 << MODE_SHIFT; //10后面跟30个0 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); } } //获取mode public static int getMode(int measureSpec) { //保留高2位,剩下30个0 return (measureSpec & MODE_MASK); } //获取size public static int getSize(int measureSpec) { //替换高两位00,测量保留低30位 return (measureSpec & ~MODE_MASK); } }我留下了比较重要的中的作用三个方法:
makeMeasureSpec。用于生成一个MeasureSpec,温故生成的而知方式就是size+mode,得到一个32位的测量int值。 获取mode。中的作用也就是温故取前2位的值作为mode。 获取size。服务器托管而知也就是测量取后30位的值作为size。至此,我们至少知道了MeasureSpec是一个32位的int值,高2位为mode(测量模式),低30位为size(测量大小)。
这么做的目的主要是避免过多的对象内存分配。
所以我们可以大致猜测,这个MeasureSpec就是用来标记View的测量参数,其中测量模式可能和View具体怎么显示有关,而测量大小就是值的View实际大小。
当然,这只是我们的初步猜测。
要搞清楚具体信息,就要从View树的绘制测量开始说起。
DecorView的测量
上文说到,测量代码是从ViewRootImpl的云服务器measureHierarchy开始的,然后会执行到performMeasure方法:
private void measureHierarchy(){ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }很明显,在这里就会进行第一次MeasureSpec的计算,并且传给了下层的mView,也就是DecorView。
那我们就来看看DecorView的MeasureSpec测量规格计算方式:
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window cant 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有关,其实也就是跟Window的调整有关,如果Window是子窗口,那么就可以调整,比如Dialog的宽高设置为WRAP_CONTENT,那么DecorView对应的测量规格就是AT_MOST。
到此,我们也可以初步得到这个测量规格mode的含义:
如果View的值是确定大小,比如MATCH_PARENT或者固定值,那么它的测量模式就是MeasureSpec.EXACTLY。亿华云计算 如果View的值是自适应,比如WRAP_CONTENT,那么它的测量模式就是 MeasureSpec.AT_MOST。具体是不是这样呢?我们继续到下层View一探究竟。
View/ViewGroup的测量
对于具体的View/ViewGroup 测量,就涉及到另外的一个方法measureChildWithMargins,这个方法也是在很多布局中会看到,比如LinearLayout。
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); }代码不多,首先获取子View的LayoutParams。然后根据 padding、margin、width 以及 parentWidthMeasureSpec 算出宽的测量模式——childWidthMeasureSpec。
高度测量模式同理。
到此,我们的认识又前进了一步,对于子View的测量模式MeasureSpec肯定是和两个元素有关:
子View的LayoutParams(包括margin,width) 父View的MeasureSpec (再加上padding)继续看看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 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 cant 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 cant 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); }代码其实很简单,就是对子View的LayoutParams和父View的specMode、specSize,共同计算出子View的MeasureSpec。
举其中一个例子,当父view的测量模式为MeasureSpec.EXACTLY,子View宽的LayoutParams为MATCH_PARENT。想象一下,这种情况,子View的宽肯定就会占满父View的大小,所以子View的测量模式中的mode肯定就是确定值,为MeasureSpec.EXACTLY,而大小就是父View的大小了。对应的代码就是:
case MeasureSpec.AT_MOST: if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; }综合所有的情况,很经典的一张表格就来了:
这里我们也可以明确了MeasureSpec中mode的含义:
MeasureSpec.EXACTLY。父View可以确定子View的精确大小,比如子View大小是固定的值,在所有的情况下都会是EXACTLY模式。 MeasureSpec.AT_MOST。父View给定一个最大的值,意思是子View大小可以不确定,但是肯定不能超过某个最大的值,例如窗口的大小。 MeasureSpec.UNSPECIFIED。父View对子View完全没限制,要多大给多大。这个模式似乎听起来有点奇怪?待会我们再细谈。到此,似乎就结束了?当然没啦,获取子View的MeasureSpec之后,子View又会怎么处理呢?
View对于MeasureSpec的处理
继续上文,测量子View的测量规格之后,会调用child.measure方法。
protected void measureChildWithMargins() { 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); } public final void measure(int widthMeasureSpec, int heightMeasureSpec) { onMeasure(widthMeasureSpec, heightMeasureSpec); }child.measure方法也就是View的measure方法,也就是走到了onMeasure方法,继续看看:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { if (optical != isLayoutModeOptical(mParent)) { measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }哦~最后原来是给子View的measuredWidth和measuredHeight赋值了,所赋的值就是getDefaultSize方法返回的大小。
而这个measuredWidth是干嘛的呢?搜索一下:
public final int getMeasuredWidth() { //MEASURED_SIZE_MASK用于限制大小的 return mMeasuredWidth & MEASURED_SIZE_MASK; }这不就是我们获取view的大小调用的方法吗?所以小结一下:
父view通过父View的MeasureSpec和子View的LayoutParams算出了子View的MeasureSpec。 然后子View通过MeasureSpec计算了measuredWidth 而这个measuredWidth也就是我们可以获取View宽高所调用的方法。最后就是看看getDefaultSize方法干了啥,也就是验证MeasureSpec中size是不是就是我们要获取的View的宽高呢?
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec) public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }可以看到,在AT_MOST和EXACTLY这两种常用的情况下,确实是等于测量大小specSize的。
只是在一个特殊情况,也就是UNSPECIFIED的时候,这个大小会等于getSuggestedMinimumWidth()方法的大小。
问题来了,UNSPECIFIED模式到底是啥,getSuggestedMinimumWidth()方法又做了什么?
UNSPECIFIED
很多文章会忽略这个模式,其实它也是很重要的,在前两天的讨论群中,我们还讨论了这个问题,一起看看吧~
首先,我们看看什么时候会存在UNSPECIFIED模式呢?它的概念是父View对子View的大小没有限制,很容易想到的一个控件就是ScrollView,那么在ScrollView中肯定有对这个模式的设置:
@Override 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 usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed; final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec( Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal), MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }没错,在ScrollView中重写了measureChildWithMargins方法,比对下刚才ViewGroup的measureChildWithMargins方法,发现有什么不对了吗?
childWidthMeasureSpec的计算没有什么变化,还是调用了getChildMeasureSpec方法,但是childHeightMeasureSpec不对劲了,直接调用了makeSafeMeasureSpec方法生成了MeasureSpec,而且!而且!直接把SpecMode设置成了MeasureSpec.UNSPECIFIED。
也就是对于子View的高度是无限制的,这也符合ScrollView的理念。
所以当ScrollView嵌套一个普通View的时候,就会触发刚才getDefaultSize中UNSPECIFIED的逻辑,也就是View的实际大小为getSuggestedMinimumWidth的大小。
继续看看getSuggestedMinimumWidth到底获取的是什么大小:
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }就一句代码:
如果view的背景为null,则等于最小宽度mMinWidth。 如果view的背景不为null,则等于最小宽度和 背景的最小宽度 中取较大值。所以如果View没有设置背景,没有设置mMinWidth,那么ScrollView嵌套View的情况,View的宽度就是为0,即使设置了固定值也没用。
这只是UNSPECIFIED在普通View中的处理情况,不同的情况对UNSPECIFIED的处理方式都不一样,比如TextView、RecycleView等等。
下次会专门出一篇UNSPECIFIED的文章,到时候见。
总结
今天回顾了MeasureSpec的相关知识点:
MeasureSpec的基本概念: MeasureSpec为一个32位的int值。 SpecMode为高两位,一共三种模式,代表父View对子View的大小限制模式,比如最大可用大小——AT_MOST。SpecSize为低30位,代表父View给子View测量好的宽高。这个宽高大概率等于View的实际宽高,但是也有例外情况,也就是UNSPECIFIED的情况。
测量流程中的MeasureSpec:
View输的测量流程开始于ViewRootImpl的measureHierarchy,也是在这里开始了第一次MeasureSpec的计算。 第一次MeasureSpec的计算也就是DecorView的MeasureSpec计算,是通过自身的LayoutParams相关,也就是和Window大小有关。 然后就开始子View/ViewGroup的MeasureSpec计算,是通过父View的MeasureSpec和子View的LayoutParams相关。 计算完子View的MeasureSpec之后,就开始调用onMeasure方法,计算出View的实际大小。 如果是UNSPECIFIED模式,实际大小为。否则实际大小就等于计算好的specSize。参考
《Android开发艺术探索》
本文转载自微信公众号「码上积木」,作者积木zz。转载本文请联系码上积木公众号。
很赞哦!(157)
下一篇: 4、注册门槛低
相关文章
- 小白注册网站域名该怎么办?有什么步骤?
- 浅说Synchronized的底层实现原理
- 从单体架构到分布式数据持久化,ORM 框架之 Mybatis
- 虚函数真的就那么慢吗?它的开销究竟在哪里?来看这4段代码!
- 公司名字不但要与其经营理念、活动识别相统一,还要能反映公司理念,服务宗旨、商品形象,从而才能使人看到或听到公司的名称就能产生愉快的联想,对商店产生好感。这样有助于公司树立良好的形象。
- 使用倒排索引极速提高字符串搜索效率
- 一篇带个你Spring Cloud微服务架构学习
- PLC 编程语言的优劣,哪种语言更适合编程
- 最后提醒我们,域名到期后要及时更新域名,否则可能会丢掉域名,每次抢先注册都不会成功。
- 大幅提高生产力:你需要了解的十大Jupyter Lab插件