view的measure过程
前言
上一篇我们梳理了DecorView加载流程,其中涉及到measure、layout、draw方法,这一篇详细介绍view的measure过程。measure的过程主要用于测量一个View的大小,在层级嵌套的View中,View大小首先取决于父View的设置大小的方式
,然后与自身的需要大小
也有关。在这样的场景中Android将View的测量模式和模式下的大小封装成一个MeasureSpec
对象 ,然后结合自身LayoutParams
去设置一个View的大小。
MeasureSpec
如果你看过View测量相关的知识,你应该知道MeasureSpec,MeasureSpec是View类的静态内部类,它由一个int 32为组成,高2位表示SpecMode,测量模式;低30位表示SpecSize,某种测量模式下的规格大小, android对它的解释是封装了从父视图传递给子视图的布局要求。
MeasureSpec中的3种测量模式
UNSPECIFIED
:父 View 没有施加任何限制给子 View,可以是子 View 想要的任何尺寸,这样一般用于系统测量,用的较少EXACTLY
:父View已确定子View的确切大小。不管怎样,子View都会受到那些限制,也就是精确模式AT_MOST
:子View的大小可以达到指定的大小,也就是最大模式
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@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;
...
}
DecorView
这里我们还是从DecorView开始分析,看看它的具体测量方法,要知道每一个View都有一个MeasureSpec,这是父视图对子视图的限制和要求,根视图也不例外,我们看一下根视图DecorView的MeasureSpec生成规则,相关代码在ViewRootImpl
类中
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
上面就是DecorView的MeasureSpec生成方式,主要看看getRootMeasureSpec方法
根据源码,具体看看其生成的规则:
- Window的模式不管是MATCH_PARENT还是WRAP_CONTENT,大小都是
windowSize
:窗口的可用宽度或高度;但对应的测量模式不一样,一个是精确模式一个是最大模式 - 如果没有任何限制那么直接使用窗口的宽高,测量模式为精确模式
windowSize
:窗口的可用宽度或高度
rootDimension
:窗口的宽或高设置的类型MATCH_PARENT或WRAP_CONTENT
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window无法调整大小,强制根视图为窗口的可用宽度或高度,模式为精确模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//Window可以调整大小,设置根视图为大小为窗口的可用宽度或高度,模式为最大模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window 为精确尺寸,强制根视图大小为根部尺寸,模式为精确模式
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
上面是DecorView的MeasureSpec生成过程和规则,我们看下DecorView怎么为子View生产MeasureSpec,以及保存自己的大小。DecorView是继承FrameLayout
,在DecorView中调用了父类的onMeasure()
方法,那么具体我们看FrameLayout的onMeasure方法
看下面源码
- 首先判断了
measureMatchParentChildren
的值,如果FrameLayout中onMeasure的参数是DecorView传递过来的,那么MeasureSpec在DecorView中经过转换后一定为EXACTLY
(可以看源码在DecorView的onMeasure里面),所以measureMatchParentChildren为false。当用户自定义一个FrameLayout属性为WRAP_CONTENT时候measureMatchParentChildren才为true
for
循环子View开始调用measureChildWithMargins
测量子View大小,并找到子View中的最大宽高保存,用于决定DecorView最后的宽高- 测量完子View后,调用
setMeasuredDimension()
保存DecorView的最后大小,其中会调用resolveSizeAndState
来计算具体的宽高大小。
接下来我们具体看看resolveSizeAndState
方法
public class FrameLayout extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//如果宽高中有一个不是精确模式,那么就返回true;如果是DecorView传递过来的,必定都是精确模式,为false。
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//使用约束MeasureSpec对子View进行测量,并得到子Viewd的LayoutParams
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//在所有子View中找到最大的宽和高
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
//DecorView不会走这个逻辑,因为DecorView的onMeasure()流程会将MeasureSpec准换成EXACTLY,只有当用户自布局一个FrameLayout属性为WRAP_CONTENT,并且宽高中有为MATCH_PARENT时才添加到集合中
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// 对Padding做一些处理
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// 获取定义的最小宽高和maxHeight相比,去一个大的
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//根据maxWidth以及widthMeasureSpec、childState得到具体的宽高并保存
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
if (count > 1) {
//当FrameLayout的大小设置其中有一个为wrap_content
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
//如果子View的width是MATCH_PARENT
//得到子View的width,设置宽度为得到的宽,模式为精确模式
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
//如果子View的width是为WRAP_CONTENT或者某一个精确值,调用getChildMeasureSpec(),具体看下面对方法讲解
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
//同理对高度做同样的处理
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
...
}
resolveSizeAndState
首先这是一个ViewGroup的方法,方法主要用于计算具体的宽高。因为View的大小首先一般取决于两个因素:1、自身measureSpec的约束;2、子View的大小,接下来我们看看具体实现。
size
:最大宽高measureSpec
:当前View的约束和测量模式childMeasuredState
:子View的测量状态
-
当specMode时AT_MOST时,那么就会判断MeasureSpec中的size,是否比传入的size(子View中的最大size)小,其实这个判断的作用就是:
当为AT_MOST时候,子View的最大大小也不要比specSize大
-
当specMode是EXACTLY时,那么直接返回specSize作为大小,
-
当对测量模式为没有约束时,则使用size (注:这里的size,对于DecorView来说,是其最大子View的测量宽高,上面源码有讲解)
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
//当前View约束为warp_content
case MeasureSpec.AT_MOST:
//warp_content下对应的大小<最大高宽,200<300取200
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
小结:上面我们分析了DecorView的整个过程,DecorView的MeasureSpec产生取决于Window的设置,但后面又将得到的MeasureSpec转成精确模式。在FrameLayout中调用measureChildWithMargins测量子View大小。然后调用resolveSizeAndState()来确定最后的宽高。这个流程中其实要知道为什么要将AT_MOST的模式转成EXACTLY,看resolveSizeAndState中的分析你就知道,其实就是为了让子View受到父View的限制,不管是AT_MOST还是EXACTLY其实就是让子View不要超过父View的大小
。
ViewGroup
上面介绍了DecorView的和其子类的MeasureSpec创建过程,那如果这个时候DecorView的子View是一个容器类ViewGroup,那么接下来ViewGroup怎么根据DecorView传递过来的MeasureSpec去测量自己的大小,以及怎么设置子View的MeasureSpec。那么我们看看ViewGroup为我们提供了哪些方法。
ViewGroup是一个抽象类
,其实它本身是没有实现View的onMeasure
方法的,作为一个容器类它将onMeasure给到了它的实现类
去完成。让实现类根据需要对自己进行大小设置以及对子View大小的测量。但在ViewGroup中提供可3个方法帮助我们对View进行测量。
measureChildren
measureChildren方法很简单,如果当前View不为GONE的就进行测量,具体测量方法调用measureChild。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
measureChild
对于这个方法和measureChildWithMargins的区别就是获取的LayoutParams
不一样,为了让View支持Margin,我们可以使用measureChildWithMargins方法,在我们自定义View的时候,需要支持Margin的需要重写generateLayoutParams方法,具体原理看这篇文章,说的很清楚了
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChildWithMargins
关键方法还是getChildMeasureSpec
。
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);
}
getChildMeasureSpec
在之前的版本里面MeasureSpec为UNSPECIFIED,View布局为MATCH_PARENT和WRAP_CONTENT返回都为0,但现在有版本判断。targetSdkVersion<23就为0,不然就返回父容器可使用大小
static boolean sUseZeroUnspecifiedMeasureSpec = false;
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
getChildMeasureSpec
是一个指导性方法,非常关键。它的作用就是根据父View的MeasureSpec和自身LayoutParams去设置自己的测量模式和模式下的Size,也就是设置自己的MeasureSpec
这个值就会在onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法的参数里面得到。
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) {
// 父容器规格是精确大小
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子View设置的是精确大小,如10dp
//size:就是自己设置的大小
//mode: 模式为精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View设置的MATCH_PARENT,希望和父View一样大
//size:使用父View的大小
//mode: 父size已经确定,模式为精确模式,
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View设置的WRAP_CONTENT,想要自己决定有多大,但是不能大于父View
//size:使用父View的大小
//mode: 父size已经确定,模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父容器规格是最大尺寸
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//子View设置的是精确大小,如10dp
//size:就是自己设置的大小
//mode: 模式为精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View设置的MATCH_PARENT,希望和父View一样大
//size:使用父View的大小
//mode: 模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View设置的WRAP_CONTENT,想要自己决定有多大,但是不能大于父View
//size:使用父View的大小
//mode: 模式为最大模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父容器规格没有做限制
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
//子View设置的是精确大小,如10dp
//size:就是自己设置的大小
//mode: 模式为精确模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View设置的MATCH_PARENT,希望和父View一样大
//size:targetSdkVersion<23?0:size
//mode: UNSPECIFIED模式,不施加任何约束
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View设置的WRAP_CONTENT,想要自己决定有多大,但是不能大于父View
//size:targetSdkVersion<23?0:size
//mode: UNSPECIFIED模式,不施加任何约束
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
对照上面流程生成如下表格,多总结看看其规律。
子View的LayoutParams\父容器SpecMode | MeasureSpec.EXACTLY | MeasureSpec.AT_MOST | MeasureSpec.UNSPECIFIED |
---|---|---|---|
精确值(dp) | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
MATCH_PARENT | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0或parentSize |
WRAP_CONTENT | AT_MOST childSize | AT_MOST parentSize | UNSPECIFIED 0或parentSize |
我们分析了上面的知识点后发现,容器类都是自己实现onMeasure, 而ViewGroup类没有onMeasure方法,MeasureSpec最开始是根据window生成并传递给DecorView,用于约束DecorView的子View,并且根据子View的状态和大小以及MeasureSpec决定了DecorView的大小,DecorView的子View的MeasureSpec也是根据父View的MeasureSpec和自身的LayoutParams决定。
View
接下来看看View的相关测量过程
getSuggestedMinimumWidth
是返回视图的建议的最小宽度,关键还是getDefaultSize()
,不管是精确模式还是最大模式都是直接返回specSize
,也就是说子View的大小取决于getChildMeasureSpec()中返回的大小和模式
,大家如果要知道View的测量方法那么就记住getChildMeasureSpec的规则。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}
小结:上面我们分析了DecorView的测量过程,以及ViewGroup和View中有关测量的方法,其中关键的是getChildMeasureSpec()方法。DecorView和其他View的MeasureSpec生产有点不同是DecorView有自己的测量方法getRootMeasureSpec,对于子view测量不管是RelativeLayout还是LinearLayout使用都getChildMeasureSpec(),理解父view的MeasureSpec和自己LayoutParams的关系就掌握了View的measure原理
MeasureSpec 与 LayoutParams 不得不说的二三事
详细说一下 MeasureSpec.UNSPECIFIED
你必须了解的LayoutParams的那些事儿