@MeasureSpecModeintmode){
if(sUseBrokenMakeMeasureSpec){
returnsize+mode;
}else{
return(size&~MODE_MASK)|(mode&MODE_MASK);
}
}
@MeasureSpecMode
publicstaticintgetMode(intmeasureSpec){
//noinspectionResourceType
return(measureSpec&MODE_MASK);
}
publicstaticintgetSize(intmeasureSpec){
return(measureSpec&~MODE_MASK);
}
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包的作用,SpecMode和specSize也是一个int值,一直SpecMode和specSize可以打包成一个MeasureSpec,一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize,需要注意的是这里提到的MeasureSpec是指MeasureSpec所代表的int值,而非MeasureSpec本身。
SpecMode有三类,每一类都有特殊的含义
UNSPECIFIED
父容器不对View有任何的限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态
EXACTLY
父容器已经检测出View所需要的精度大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent,和具体的数值这两种模式
AT_MOST
父容器指定了一个可用大小,即SpecSize,view的大小不能大于这个值,具体是什么值要看不同view的具体实现,它对应于LayoutParams中wrap_content
2.MeasureSpec和LayoutParams的对应关系
系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View的测量,但是正常情况下我们使用View指定MeasureSpec,但是尽管如此,我们也可以给View设置layoutparams,在view测量的时候,系统会将layoutparams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定view测量后的宽高,需要注意的是,MeasureSpec不是唯一由layoutparams决定的,layoutparams需要和父容器一起决定view的MeasureSpec从而进一步决定view的宽高,对于顶级view(DecorView)和普通的view来说,MeasureSpec的转换过程有些不同,对于decorview,其MeasureSpec由父容器的MeasureSpec和自身的layoutparams来决定,MeasureSpec一旦确定后,MeasureSpec就可以去为view测量了
对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有这么一段代码。
他展示了DecorViwew的MeasureSpec创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕的尺寸
childWidthMeasureSpec=getRootMeasureSpec(baseSize,lp.width);
childHeightMeasureSpec=getRootMeasureSpec(desiredWindowHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
接下来看下getRootMeasureSpec方法的实现:
privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){
intmeasureSpec;
switch(rootDimension){
caseViewGroup.LayoutParams.MATCH_PARENT:
//Windowcan'tresize.ForcerootviewtobewindowSize.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
caseViewGroup.LayoutParams.WRAP_CONTENT:
//Windowcanresize.Setmaxsizeforrootview.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
break;
default:
//Windowwantstobeanexactsize.Forcerootviewtobethatsize.
measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
break;
}
returnmeasureSpec;
}
通过上述代码,DecorView的MesourSpec的产生过程就很明确了,具体来说其遵守了如下格式,根据layoutparams的宽高的参数来划分
LayouParams.MATCH_PARENT:
精确模式,大小就是窗口的大小
LayouParams.WRAP_CONTENT:
最大模式,大小不定,但是不能超出屏幕的大小
固定大小(比如100dp):
精确模式,大小为LayoutParams中指定的大小
对于普通的View来说,这里是指我们布局中的View,View的measure过程由ViewGroup传递而来,先看下ViewGroup的measureChildWithMargis方法
protectedvoidmeasureChildWithMargins(Viewchild,
intparentWidthMeasureSpec,intwidthUsed,
intparentHeightMeasureSpec,intheightUsed){
finalMarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();
finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin
+widthUsed,lp.width);
finalintchildHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin
+heightUsed,lp.height);
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
上述的方法会对子元素进行measure,在调用子元素的measure方法之前会通过getChildMeasureSpec方法得到子元素的MesureSpec,从代码上看,很显然,子元素的MesureSpec的创建和父容器的MesureSpec和子元素的LayoutParams有关,此外,还和view的margin有关,具体可以看下ViewGroup的getChildMeasureSpec方法
publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){
intspecMode=MeasureSpec.getMode(spec);
intspecSize=MeasureSpec.getSize(spec);
intsize=Math.max(0,specSize-padding);
intresultSize=0;
intresultMode=0;
switch(specMode){
//Parenthasimposedanexactsizeonus
caseMeasureSpec.EXACTLY:
if(childDimension>=0){
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize.Sobeit.
resultSize=size;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
//Parenthasimposedamaximumsizeonus
caseMeasureSpec.AT_MOST:
if(childDimension>=0){
//Childwantsaspecificsize...sobeit
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize,butoursizeisnotfixed.
//Constrainchildtonotbebiggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
//Parentaskedtoseehowbigwewanttobe
caseMeasureSpec.UNSPECIFIED:
if(childDimension>=0){
//Childwantsaspecificsize...lethimhaveit
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize...findouthowbigitshould
//be
resultSize=View.sUseZeroUnspecifiedMeasureSpec?
0:
size;
resultMode=MeasureSpec.UNSPECIFIED;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize....findouthow
//bigitshouldbe
resultSize=View.sUseZeroUnspecifiedMeasureSpec?
0:
size;
resultMode=MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspectionResourceType
returnMeasureSpec.makeMeasureSpec(resultSize,resultMode);
}
上述方法不难理解,他的主要作用是根据父容器的MeasureSpec同时结合view本身来layoutparams来确定子元素的MesureSpec,参数中的pading是指父容器中已占有的控件大小,因此子元素可以用的大小为父容器的尺寸减去pading,具体代码
intspecSize=MesureSpec.getSize(spec);
intsize=Math.max(0,specSize-pading);
getChildMeasureSpec清楚的展示了普通View的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeaureSpec的创建规则,更加清晰的理解getChildMeasureSpec的逻辑,这里提供一个表,表中对getChildMeasureSpec的工作原理进行了梳理,表中的parentSize是指父容器中目前可使用的大小:
这张表暂时不画,可以到书中看182页
针对这张表,这里再做一下说明。
前面已经提到,对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,那么针对不同的父容器和Viev本身不同的LayoutParams,View就可以有多种MeasureSpec。
这里简单说一下,当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpee都是精确模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。
当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化,并且大小不能超过父容器的剩余空间,可能读者会发现,在我们的分析中漏掉了UNSPECIFIED模式,那是因为这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。
通过这张表可以看出,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元亲测量后的大小了。
需要说明的是,表中并非是什么经验总结,它只是getcchildMeasureSpec
这个方法以表格的方式呈现出来而已
3.View的工作流程
View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽/高,layout确定View的最终宽/高和四个顶点的位置,而draww则将View绘制到屏幕上。
1.measure过程
measure过程要分情况来看,如果只是一个原始的View,那么通过measure方法就可以完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程,下面针对这两种情况分别讨论
1.View的measure过程
View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这就意味着子类不能重写此方法,在View的measure方法中去调用View的onMesure方法,因此只需要看onMeasure的实现即可,View的onMesure方法如下所示:
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),height