对于MeasureSpec,你的认识有多少呢?
成都创新互联专注于扎赉诺尔网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供扎赉诺尔营销型网站建设,扎赉诺尔网站制作、扎赉诺尔网页设计、扎赉诺尔网站官网定制、小程序开发服务,打造扎赉诺尔网络公司原创品牌,更为您提供扎赉诺尔网站排名全网营销落地服务。
首先,我们看下这个类:
- 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);
- }
- }
我留下了比较重要的三个方法:
至此,我们至少知道了MeasureSpec是一个32位的int值,高2位为mode(测量模式),低30位为size(测量大小)。
这么做的目的主要是避免过多的对象内存分配。
所以我们可以大致猜测,这个MeasureSpec就是用来标记View的测量参数,其中测量模式可能和View具体怎么显示有关,而测量大小就是值的View实际大小。
当然,这只是我们的初步猜测。
要搞清楚具体信息,就要从View树的绘制测量开始说起。
上文说到,测量代码是从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 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有关,其实也就是跟Window的调整有关,如果Window是子窗口,那么就可以调整,比如Dialog的宽高设置为WRAP_CONTENT,那么DecorView对应的测量规格就是AT_MOST。
到此,我们也可以初步得到这个测量规格mode的含义:
具体是不是这样呢?我们继续到下层View一探究竟。
对于具体的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肯定是和两个元素有关:
继续看看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 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);
- }
代码其实很简单,就是对子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的含义:
到此,似乎就结束了?当然没啦,获取子View的MeasureSpec之后,子View又会怎么处理呢?
继续上文,测量子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的大小调用的方法吗?所以小结一下:
最后就是看看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模式呢?它的概念是父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没有设置背景,没有设置mMinWidth,那么ScrollView嵌套View的情况,View的宽度就是为0,即使设置了固定值也没用。
这只是UNSPECIFIED在普通View中的处理情况,不同的情况对UNSPECIFIED的处理方式都不一样,比如TextView、RecycleView等等。
下次会专门出一篇UNSPECIFIED的文章,到时候见。
今天回顾了MeasureSpec的相关知识点:
SpecSize为低30位,代表父View给子View测量好的宽高。这个宽高大概率等于View的实际宽高,但是也有例外情况,也就是UNSPECIFIED的情况。
测量流程中的MeasureSpec:
参考
《Android开发艺术探索》
本文转载自微信公众号「码上积木」,作者积木zz。转载本文请联系码上积木公众号。
网页题目:温故而知新 MeasureSpec在View测量中的作用
URL分享:http://www.shufengxianlan.com/qtweb/news0/516650.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联