@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);
}
}
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。
MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽高。另外,++对于DecorView和普通View来说,MeasureSpec的转换过程略有不同。++ 对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。MeasureSpec确定之后,onMeasure就可以确定View的测量宽高。
MeasureSpec遵循的规则:
-
LayoutParams.MATCH_PARENT:精确模式,大小就是窗口或父容器的大小;
-
LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口大小;
-
固定大小(比如100dp):精确模式,大小为LayoutParams中指定的大小。
V
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
iewGroup的measureChildWithMargins方法
/**
-
Ask one of the children of this view to measure itself, taking into
-
account both the MeasureSpec requirements for this view and its padding
-
and margins. The child must have MarginLayoutParams The heavy lifting is
-
done in getChildMeasureSpec.
-
@param child The child to measure
-
@param parentWidthMeasureSpec The width requirements for this view
-
@param widthUsed Extra space that has been used up by the parent
-
horizontally (possibly by other children of the parent)
-
@param parentHeightMeasureSpec The height requirements for this view
-
@param heightUsed Extra space that has been used up by the parent
-
vertically (possibly by other children of the parent)
*/
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);
}
上述方法会对子元素进行measure,在调用子元素的measure方法之前,会先通过getChildMeasureSpec方法来获取子元素的MeasureSpec。显然,子元素的MeasureSpec的创建和父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin和padding有关,具体情况如下:
/**
-
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
-