一、MeasureSpec概述
MeasureSpec内部封装了父布局传递给子View的布局要求,它可以表示一个宽或高,它是由size和mode组成,在View视图中,MeasureSpec相当于View的测量规格,它内部对大小和模式进行了一一封装。
1、MeasureSpec的三种模式
MeasureSpec.EXACTLY:当父View已经检测出子View的大小,此时,MeasureSpec的模式就是这个。
MeasureSpec.AT_MOST:父View还没有检测出子View所需的精确大小,但子View却指定了它的最大值,不得超过指定的size,这时,MeasureSpec的模式为AT_MOST。
MeasureSpec.UNSPECIFIED:父View不对子View大小进行限制时,MeasureSpec的模式为UNSPECIFIED,通常该模式会常用于Android系统的内部。
二、MeasureSpec源码解析
1、MeasureSpec测量子View之measureChildWithMargins方法
在测量子View的measureChildWithMargins方法中,首先通过子View获取了布局参数LayoutParams,接下来通过getChildMeasureSpec获取了子View宽度的MeasureSpec值,而子View宽度的MeasureSpec值的计算,是根据父View宽度的MeasureSpec、父View的paddingLeft、paddingRight、子View的marginLeft、子View的marginRight、父View已经使用的宽度width和子View设置的宽度计算出来的,这时getChildMeasureSpec方法就可以根据以上这些值,最终计算出子View宽度的MeasureSpec。
而子View高度的MeasureSpec值,也是通过此方法,并根据父View高度的MeasureSpec、父View的paddingTop、父View的paddingBottom、子View设置的marginTop、子View设置的marginBottom、父View已经使用的高度和子View设置的高度,最终计算出子View高度的MeasureSpec。
最后,根据子View宽度和高度的MeasureSpec,通过View的measure方法,完成对子View的测量操作。可见,在自定义View中,子View的宽和高,是根据父View的相关参数以及子View设置的参数进行计算的。
总体概括,测量子View的大小,大体分为三个步骤。首先,通过子View获取到LayoutParams;接下来根据父View的相关参数以及子View设置的布局参数,分别获取到子View宽和高的MeasureSpec;最后,再根据获取到的子View宽和高的MeasureSpec,执行子View的测量方法,这样子View就测量完成了。其中,getChildMeasureSpec方法就相当于对子View宽和高的MeasureSpec的计算过程。
measureChildWithMargins方法在执行时,会接收5个参数,这5个参数的含义如下。
其中,child为子View,parentWidthMeasureSpec相当于父View(也就是父容器或父布局)宽度的MeasureSpec,顾名思义,parentHeightMeasureSpec就不用再解释了;而widthUsed和heightUsed,是父View已经使用的宽度和高度大小。
2、测量子View宽和高MeasureSpec值的getChildMeasureSpec方法
该方法首先根据父View宽或高的MeasureSpec获取Mode和Size值,接下来根据父View在水平或垂直方向,已经被使用的宽度或高度大小,得到子View的宽或高大小size。其中,specSize为父View宽或高的总大小,padding为父View已经被使用的宽或高大小,这两者相减,就可以得到子View的宽或高大小size,如该方法前三行代码所示。
接下来将会根据父View的specMode值,来创建子View的MeasureSpec。由于specMode值会有三种类型,所以,不同类型的情况下,对子View的处理也不同。
(1)父View的mode处于EXACTLY模式下,子View的MeasureSpec处理
当父View的specMode值为EXACTLY时,首先检查子View宽或高是否被指定大小,如果子View的宽或高被指定了大小,那么,childDimension值将不等于LayoutParams中的MATCH_PARENT和WRAP_CONTENT,因此,当指定子View宽度或高度后,此判断条件将会为true,故子View的宽度或高度,就采用指定的值,而mode模式,由于已经指定子View大小,所以此时该模式为精确模式,即EXACTLY。
当子View宽度或高度被设置为MATCH_PARENT填充父View时,那么,子View的大小就是上面得到的这个size值,也就是父View总大小,减去父View已经使用的部分,剩下的值就是子View的大小。在这种情况下,由于值也是属于通过精确计算而得出的,那么,子View的模式同样为精确模式EXACTLY。
当子View宽度或高度被设置为WRAP_CONTENT包裹内容时,子View的大小同样采用上面的这个size值,即父View剩余大小,但是,与前者MATCH_PARENT不同的地方是,在这种情况下,子View的mode值被设置为AT_MOST,也就是说,由于子View没有指定具体大小,而是包裹内容时,但指定了最大值,所以,这种情况下采用该模式。
关于父View的specMode值为EXACTLY时,关于子View大小的计算处理以及mode值设定,代码如下所示。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
//根据父View宽或高的MeasureSpec值,获取父View宽或高的大小
int specSize = MeasureSpec.getSize(spec);
//根据父View宽或高的大小,以及父View已经被使用的款或高,得到子View的宽或高大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//以父View的specMode为基准进行判断
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
//检查是否指定了子View的宽度或高度
if (childDimension >= 0) {
//如果指定了子View的宽度或高度,那么,子View的宽度或高度就采用指定的。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//当子View宽度或高度被设置为填充父View时,子View宽度或高度大小就是父View的剩余大小。
resultSize = size;
//由于剩余大小同样为精确值,所以这里同样采用EXACTLY模式。
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;
}
(2)父View的mode处于AT_MOST模式下,子View的MeasureSpec处理
当父View的mode处于此模式下,子View可以使用父View的大小。首先,当子View被指定大小时,也就是符合第一个判断条件时,那么,子View将采用指定的大小,并且mode模式将会为精确模式EXACTLY。
当子View大小被指定为填充父View,即MATCH_PARENT时,子View大小将采用父View所剩余的大小,模式也就是AT_MOST。当子View大小被指定为包裹内容,即WRAP_CONTENT时,则size和mode指定,与前者相同。
通常情况下,关于自定义View过程中,只会进行以上两种模式的处理,而第三个模式UNSPECIFIED则不会被使用到,该模式主要在Android系统源码中会经常用到。关于父View的mode处于AT_MOST模式时,子View的大小和mode值设定,代码如下所示。
当完成对子View的大小和mode模式设置后,接下来将会通过MeasureSpec中的静态方法makeMeasureSpec方法返回子View的MeasureSpec宽或高的值,代码如下所示。
通过分析以上源代码可以看出,子View的MeasureSpec大小,是由父View的MeasureSpec以及子View自身的布局设定及参数,来共同决定的。
综上所述,子View的MeasureSpec创建规格如下表所示。当父View的MeasureSpec中的mode处于不同模式下,子View的大小也会有所不同。除了UNSPECIFIED模式,当父View处于EXACTLY和AT_MOST模式下,子View如果设置了指定的大小,那么,子View这时的大小,就是指定的大小;当父View处于EXACTLY和AT_MOST模式下,如果子View并没有直接指定大小,而是只设置了MATCH_PARENT或WRAP_CONTENT,那么,子View这时的大小,就是父View的剩余大小。
三、onMeasure源码解析
View的onMeasure方法源代码如下所示,大致调用过程可分为getSuggestedMinimumWidth(Height)、getDefaultSize和setMeasuredDimension这三个过程。其中,setMeasuredDimension方法用于设置View的宽度和高度;getDefaultSize方法用于获取View的宽度和高度;而调用getDefaultSize方法的同时,又会去调用getSuggestedMinimumWidth和getSuggestedMinimumHeight分别去获取View的宽和高的最小值。
分析View的onMeasure过程的源码,我们可以大致根据这三个过程进行分析,首先分析获取View宽和高的最小值。
1、getSuggestedMinimumWidth(Height)获取View的最小值
该方法在获取View的最小宽度时,首先检查了View是否设置了背景,如果View尚未设置背景,那么,View的最小值便会为指定的View最小值或默认最小值。相反,如果View设置了背景,则会根据View最小值以及背景的宽度,得到它们两者的最大值作为View的最小宽度进行返回。同理,获取View的最小高度,也是与该方法的思想是一致的。
2、getDefaultSize获取View的宽和高
getDefaultSize方法为获取View的宽度和高度,该方法调用时会接收两个参数。其中,size为View的最小宽度或高度;measureSpec为View在测量阶段计算出的MeasureSpecWidth或Height。
首先会根据View在测量阶段计算出的measureSpec获取到当前View的measureSpec模式和测量阶段的specSize大小。接下来将会检查当前View的MeasureSpec模式,通常情况下,当View的MeasureSpec模式为AT_MOST或EXACTLY时,View的宽度或高度大小,就是View在测量阶段所得到的MeasureSpecWidth或Height大小,最后,返回View在测量阶段所得到的宽度或高度。关于getDefaultSize方法,其源代码如下所示。
3、setMeasuredDimension设置View的宽和高
设置View宽和高的源代码如下所示。