接上文,下面讲讲View工作的三大流程之一:
Measure流程
一.Measure 过程说明:
- ViewRootImpl的performTraversals方法开始View的工作流程,里面有一系列的判断,当前是否需要Measure,如果需要,则执行ViewRootImpl的performMeasure方法开始测量。performMeasure方法会先调用DecorView的measure方法,实际上就是调用View的measure(这个方法是final的,不允许子类重写),然后View的measure方法会判断是否需要测量(3.3分析),如果需要测量回执行View的onMeasure方法,同时ViewGroup(如果是ViewGroup的话)的宽高限制(widthMeasureSpec,heightMeasureSpec)也会传递到onMeasure,onMeasure方法会真正完成View的测量工作,ViewGroup的onMeasure方法又会遍历调用子View的measure方法,完成子View的测量过程,这样一层层传递详情,完成View树上所有View的测量,最后通过setMeasureDimension方法保存最后的测量宽高到mMeasureWidth,mMeasureHeight
二.MeasureSpec
- Measure流程是三个流程里面最复杂的,先说下一个概念MeasureSpec MeasureSpec
封装了父View传递给子View的布局要求,它是根据子View的layoutParams属性(layout_windth,layout_height等等)和父View自身的宽高生成的一个尺寸需求。当前View会根据这个值来设置自己的测量宽高(当然自定义View的时候你也可以不用它,直接设置宽高,当然最好不要这么做)。
MeasureSpec
本身不代表任何值,它相当于一个工具类,用来将specMode测量模式和specSize规格大小打包生成一个32位的int值,它的高两位代表了specMode规格模式和低30为数值对应specSize具体的尺寸值。MeasureSpec也提供了解包的方法,获得specMode和specSize的对应的具体的int值
specMode 有三种取值 MeasureSpec.EXACTLY 父View希望子VIew的大小是确定的,就是specSize的大小
MeasureSpec.AT_MOST 父View希望子View的大小最大是specSIze对应的值
MeasureSpec.UNSPECIFIED 父View希望子View大小完全按照自己设置的值来决定
具体代码如下:
private static final int MODE_SHIFT=30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//size和mode位于产生一个32位int值MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
三.源码分析:
3.1 performMeasure方法 开始Measure流程
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
3.2 getRootMeasureSpec方法 计算ViewRoot对根视图DecorView的宽高限制
这个mView实际就是DecorView的对象,上面说过了,这是Activity界面的根视图
childWithMeasureSpec 和childHightMeasureSpec是调用下面这个方法获取的
//windowSize
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//默认情况下是MACTCH_PARENT(这也是我们平常在一个Activity里什么都不设置,就默认显示全屏的原因)
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;
}
如果我们没有修改窗口属性,默认情况的windowSize是屏幕宽高,rootDimension是LayoutParams.MACTH_PARENT
已屏幕宽度1080,设置竖屏,默认情况下得到的childWithMeasureSpec=1073742904 ( 二进制:01000000000000000000010000111000);
高两位01表示specMode EXACITY 根View的parent ViewRootImpl希望根View DecorView的大小是确定的就是低30位(000000000000000000010000111000)=1080
3.3 View的measure方法
mView.measure就是调用view的measure方法
看一下view的measure源码
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是否是视觉边界布局模式,这样layoutmode为LAYOUT_MODE_OPTICAL_BOUNDS为true,默认是clipBound模式
boolean optical = isLayoutModeOptical(this);
//当前视图和父视图只能有一个是边界布局模式才会执行下面的方法。
//视觉边界布局模式只对设置了9patch图片背景的View的有效果,其他情况和默认相
//Insets.NONE,left right top bottom都是0.
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
//key保存了父View传递下来的宽高限制long型8个字节(高4个字节代表widthMeasureSpec ,低4个字节代表heightMeasureSpec )
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//mMeasureCache LongSparseLongArray容器对象,存储View在不同的widthMeasureSpec 和heightMeasureSpec 的情况下的测量结果。
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//forceLayout 当前View是否需要执行强制layout mPrivateFlags 标记了View的工作状态。
//调用View的requestLayout方法会标记这个PFLAG_FORCE_LAYOUT
//执行View layout最后会标记~PFLAG_FORCE_LAYOUT,标记不在强制布局状态
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
//宽高限制是否变更mOldWidthMeasureSpec,mOldHeightMeasureSpec默认为Integer的最小值0x80000000
//specChanged代表ViewGroup传递下来的MeasureSpec与上一次相比是否发生变化,变化了为true
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
//isSpecExactly 测量宽高限制是否都是EXACTLY模式 ,都是EXACTLY模式为true
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
//当前View的测量宽高是否都对于MeasureSpec传递下来的宽高size,都相等matchesSpecSize 为true
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
//needsLayout 是否需要重新测量
//(如果ViewGroup传递下来的MeasureSpec发生变化并且sAlwaysRemeasureExactly 为true,isSpecExactly 为false,matchesSpecSize为false有一个成立时
//needsLayout 为true);sAlwaysRemeasureExactly 在6.0以下均为false,6.0及以上为true。
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
//如果View设置了强制测量或者当前View需要测量
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
//mPrivateFlags View 状态,将标记View已经测量的位(PFLAG_MEASURED_DIMENSION_SET)置为0,清空measured dimension标识
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理(一般情况下不需要考虑)
resolveRtlPropertiesIfNeeded();
//是否能从mMeasureCache中读取测量值,如果是设置forceLayout cacheIndex =-1,否则根据MeasureSpec值计算缓存索引。
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//sIgnoreMeasureCache 忽略测量缓存,API<19时为true,即在API<19时,肯定会调用onMeasure方法
//cacheIndex < 0 forceLayout或者在测量缓存中没有找到对应的测量Spec,则重新测量。调用onMeasure方法
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
//重置标志位mPrivateFlags3 标识layout之前不需要重新测量了
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//可以从测量缓存中直接读到测量过的尺寸
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
//直接多View的mMeasureWidth和mMeasureHeight赋值
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
//如果View没有调用setMeasuredDimension方法(即mPrivateFlags 没有标记PFLAG_MEASURED_DIMENSION_SET状态,已经设置了测量尺寸,实际调用setMeasuredDimensionRaw方法时会操作mPrivateFlags 标记)会抛出异常
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//将新的测量值mMeasuredWidth,mMeasuredHeight 打包一个long对象存储到mMeasureCache里
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
说明:
a.View 的measure方法不是一调用就会进行测量的
b.View 会根据当前传递下来的MeasureSpec宽高限制是否发生变化(模式改变或者size变化),是否调用了forceLayout方法强制重新测量来判断是否重新测量,重新对测量宽高赋值。
c.如果MeasureSpec发生变化,View会在根据当前的MeasureSpec从测量缓存mMeasureCache中查询是否已经测量过了,如果测量过了,直接调用setDimensionRaw方法对View的mMeasureWidth和mMeasureHeight属性赋值
d.如果是forceLayout模式,或者API<19,或者当前传递下来的MeasureSpec没有进行测量过,那么View只能调用onMeasure方法测量一次了。
3.4 View的onMeasure方法
View 默认的onMeasure方法很简单,调用setMeasuredDimension方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//View的onMeasure默认调用setMeasureDimension方法设置View的测量宽高。
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
//视觉边界布局模式效果,忽略
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
//最后实际调用setMeasuredDimensionRaw方法
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//对mMeasuredWidth ,mMeasuredHeight 属性赋值,mPrivateFlags 标记为已经测量了。
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
看一下getDefaultSize方法,默认的测量宽高计算方法
//获得最小宽度
protected int getSuggestedMinimumWidth() {
//先判断是否有背景,无的话取mMinWidth值 ,默认为0
//如果有背景的话,取图片背景的宽度和mMinWidth的最大值作为最大宽度。
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//同最小宽度的获取
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
//计算测量宽高值
public static int getDefaultSize(int size, int measureSpec) {
//View的最小宽高值
int result = size;
//ViewGroup对View的宽高限制模式,具体怎么算的看下面的流程(3.7)
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
//UNSPECIFIED 模式取最小宽高
result = size;
break;
//AT_MOST EXACTLY模式取ViewGroup对子View的限制宽高
//这里就是我们设置自定义View时不重写onMeasure方法设置MACTH和WRAP属性完全一样的一样,所以一般情况下自定义View时我们都需重写onMeasure方法。
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
3.5 DecorView的onMeasure 方法
接着View的Measure流程,刚开始View Measure肯定调用onMeasure方法的,DecorView重写了View的onMeasure方法,看一下DecorView的onMeasure方法源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
//是否是竖屏
final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
//获取ViewRoot传递下来的宽高规格模式
final int widthMode = getMode(widthMeasureSpec);
final int heightMode = getMode(heightMeasureSpec);
boolean fixedWidth = false;
//设置窗口的属性是WARP_CONTENT的话,就会走下面代码,重新计算widthMeasureSpec 和heightMeasureSpec
if (widthMode == AT_MOST) {
final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
final int w;
if (tvw.type == TypedValue.TYPE_DIMENSION) {
w = (int) tvw.getDimension(metrics);
} else if (tvw.type == TypedValue.TYPE_FRACTION) {
w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
} else {
w = 0;
}
if (w > 0) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(w, widthSize), EXACTLY);
fixedWidth = true;
}
}
}
if (heightMode == AT_MOST) {
final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
final int h;
if (tvh.type == TypedValue.TYPE_DIMENSION) {
h = (int) tvh.getDimension(metrics);
} else if (tvh.type == TypedValue.TYPE_FRACTION) {
h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
} else {
h = 0;
}
if (h > 0) {
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(h, heightSize), EXACTLY);
}
}
}
//下面这代码没看出来是干啥的,window属性下面是有个windowOutsetBottom,但是没有找到设置值的主题,也知道的同学麻烦告诉一下
if (mOutsetBottom != null) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode != MeasureSpec.UNSPECIFIED) {
int outset = (int) mOutsetBottom.getDimension(metrics);
int height = MeasureSpec.getSize(heightMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + outset, mode);
}
}
//调用FrameLayout的onMeasure方法
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
boolean measure = false;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
//非全屏情况,设置window attribute WARP_CONTENT情况(我理解的下面这里就是让WARP_CONTENT情况下宽度不会太小,不会正好等于子控件的宽度,具体需要在研究一下,高度没有这个,使用WARP_CONTENT情况下高度最大为子空间的显示宽度)
if (!fixedWidth && widthMode == AT_MOST) {
final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
if (tv.type != TypedValue.TYPE_NULL) {
final int min;
if (tv.type == TypedValue.TYPE_DIMENSION) {
min = (int)tv.getDimension(metrics);
} else if (tv.type == TypedValue.TYPE_FRACTION) {
min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
} else {
min = 0;
}
if (width < min) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
measure = true;
}
}
}
// TODO: Support height?
if (measure) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
最后还是调用了FrameLayout的onMeasure方法。
3.6 FrameLayout的onMeasure方法
- 3.6.1
onMeasure源码分析
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取子控件的数量
int count = getChildCount();
//widthMeasureSpec heightMeasureSpec是否都是MeasureSpec.EXACTLY模式,false的时候都是EXACTLY模式
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
//清空mMatchParentChildren集合
mMatchParentChildren.clear();
//局部变量 maxHeight maxWidth 最大宽度 childState 子View的状态
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍历所有的子View
for (int i = 0; i < count; i++) {
//获取子控件
final View child = getChildAt(i);
//mMeasureAllChildren boolean值默认为false ,控制是否测量所有的子View(true是测量Visibility为GONE的子View,false不测量),可以通过setMeasureAllChildren(boolean measureAll)方法更改属性值
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//测量子View的宽高--3.6.2(ViewGroup的重要方法)
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); //子View向FrameLayout要求的布局属性
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//给maxWidth和maxHeight赋值,【重要】这里就是FrameLayout的实际宽度的一个重要指标了,maxWidth和maxHeight的最终值为FrameLayout的所有孩子的(子View的测量宽高+子View的Margin)的最大值,
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
//获取子View宽高State信息,这个State是标识是子View告诉父View,当前的测量完的宽高是否满足我的需求
childState = combineMeasuredStates(childState, child.getMeasuredState());
//父View的传递下来的MeasureSpec宽高需至少有一个不是EXACTLY模式时
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
//MeasureSpec Mode不为EXACTLY时,同时子View宽高属性为MATCH_PARENT时,将这个View加入到mMatchParentChildren集合中。
//子View为MACTH_PARENT时,FrameLayout的ViewGroup传递下来的specMode不为EXACTLY,将子View加入到mMatchParentChildren集合中,重新测量。因为到目前为止FrameLayout的最终测量宽高还没有赋值,所有MACTH的时候,子View不好确定自身的测量宽高。如果EXACLTY的话,他的测量宽高肯定是specSize的值,已确定。
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
//计算FrameLayout的padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
//得到View的最大宽高,取子View宽高+margin和最小宽高的最大值
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());
}
//mMeasureWidth,mMeasureHeight赋值
//resolveSizeAndState方法计算最终的测量值
//setMeasuredDimension View的方法,前面说过,最后调用setMeasuredDimensionRaw方法直接对View的属性mMeasureWidth,mMeasureHeight赋值
//执行完这个方法,实际View的测量宽高已经确定了
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//因为FrameLayout宽或者高不确定,导致子View宽高不确定的,重新计算MeasureSpec再调用子View的Measure方法测量一次
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//计算新的childWidthMeasureSpec,下面的Height类似
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
//子View是MATCH_PARENT这个时候要和FrameLayout宽度减去左右padding一样
//排除FrameLayout最终的测量宽度为0的情况
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
//调用makeMeasureSpec组合成childWidthMeasureSpec ,specMode为EXACTLY
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
//其他情况不变(和正常的计算方法一样--3.6.2)
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
//重新测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
- 3.6.2
measureChildWithMargins方法,ViewGroup计算子View的measureSpec主要方法之一
(还有个measureChild方法,区别就是没有计算子View的Margin)
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//实际是调用ViewGroup的getChildMeasureSpec方法,根据父View的measureSpec和子View的layoutParams来计算自己的measureSpec.具体怎么计算的见3.7。
//传递的参数有:1,父View的Spec ,2.padding(width是左右padding+左右Margin++其他子View已经使用的宽度(FrameLayout withUsed和heightUsed都是0) height是上下类似),。3.view的宽度,这个值可能是具体的值(EXACTLY)可能是MATCH_PARENT(ECACTLY或者AT_MOST),可能是WRAP_CONTENT(AT_MOST)
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);
//测量子View,看到了吗,这里View的测量继续向下传递了
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
- 3.6.3
看一下View的resolveSizeAndState方法,这个是生成当前View的实际测量值的方法
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
//解包,获得specMode specSize
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
//定义局部变量,最后返回result
final int result;
switch (specMode) {
//AT_MOST模式,父View要求当前View最大为specSize
case MeasureSpec.AT_MOST:
//父View允许的specSize 的小于View的最小宽度需求
if (specSize < size) {
//result的高1个字节为 MEASURED_STATE_TOO_SMALL 0x01000000 state告诉ViewGroup分配的尺寸太小了
//低三个字节为实际的尺寸大小
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
//注意这里,当为ECACTLY模式的时候,View的测量值就是specSize,不论多大或者多小
result = specSize;
break;
//其他情况直接等于View当前计算的最小宽高(这种情况一般不用考虑,DecorView是没有传递这种模式,一般是系统的测量和我们关系不大)
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
3.7 ViewGroup计算子View的MeasureSpec,对子View的宽高需求
getChildMeasureSpec也是ViewGroup的重要方法 计算其子View对应的SPEC
/**
* 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) {
//规格模式--EXACTLY AT_MOST UNSPECIFIED
int specMode = MeasureSpec.getMode(spec);
//测量大小
int specSize = MeasureSpec.getSize(spec);
//padding+子View的Margin(部分ViewGrop不会计算Margin,常用的都会计算)+已经使用的宽或者高
//计算View还有多少可用的尺寸,不能小于0
int size = Math.max(0, specSize - padding);
//最终的spec值由Size和Mode组成
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//父View传递下来的specMode是EXACTLY
//ViewRoot传递下来的默认情况就是这个模式
case MeasureSpec.EXACTLY:
//子View的尺寸是固定值
if (childDimension >= 0) {
//size==子View的大小
resultSize = childDimension;
//Mode是EXACTY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//子View的大小是Match_PARENT size等于View剩余的尺寸,模式是EXACTLY
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.
//子View的大小是WARP_CONTENT 模式是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
//当父View要求子View最大不能超过specSize
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
//子View设置了固定的尺寸,和ECACTLY模式下一样
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.
//子View是MACTCH_PARENT时,size是View的可用尺寸,mode是AT_MOST
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.
//子View的尺寸是WRAP_CONTENT时,size是View的可用尺寸,mode是AT_MOST
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
//打包生成子View的measureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
结论:不考虑UNSPECIFIED的情况下,Android系统自带的控件的测量宽高一般符合以下规则:
如果View设置了固定的尺寸,则不管父View的测量模式是什么,View的specMode均为ECACTLY
,最后的测量尺寸(mMeasureWidth ,mMeasureHeight)也一定的子View的设置的尺如果View的尺寸是MATCH,则如果父View的specMode是ECACTLY
则View的specMode也为EXACTLY,最后的测量尺寸(mMeasureWidth,mMesureHeight)一定是父View剩余的可用尺寸大小(可能是0);如果父View的specMode是AT_MOST View的specMode也是AT_MOST ,最后的测量尺寸(mMesureWidth,mMeasureHeight) 是View自身计算的最小内容显示大小和View剩余可用尺寸的最小值如果View的尺寸的WRAP,则不管父View的specMode ,View 的specMode均为AT_MOST ,最后的测量尺寸(mMesureWidth,mMeasureHeight)是View自身计算的最小内容显示大小(默认最小大小)和View剩余可用尺寸的最小值
3.8
- 继续下面就是测量DecorView的子类,这个根据我们设置的Activity的主题来确定的(no_title
什么的),一般情况下是一个垂直线性布局(我们用的最多的),里面会用一个id为cotent的FrameLayout
,我们setContentView
就是设置到这个View的View,测量就是再从DecorView的onMeasure方法,遍历调用调用子View的measure方法,调用ContentView的measure方法又会调用View的onMeasure方法,再开始遍历调用所有子View的measure方法,一层层的传递下去,最终调用setMeasureDimension方法设置View的测量宽高。
四.mesure流程总结
1.View的测量时从ViewRootImpl的performTraversals的开始,performTraversals方法判断是否需要测量,再调用performMeasure方法开始View的测量流程
2.ViewGroup的子类会重写View的onMeasure方法,对所有子View执行便利,计算子View的measureSpec,并调用子View的measure的方法开始子View的测量工程。View的measure方法(不能重写)又会调用onMeasure方法,如果是ViewGroup又会继续遍历下去,这样一层层的传递下去,直到便利到View树的底部,设置了View树上所有View的测量宽高为止。
3.自定义View需要重写onMeasure方法,自定义View的测量宽高的设置值(默认情况下MACTH和WRAP值的一样大的)。
4.自定义ViewGroup必须重写onMeasure 方法,自定义其所有子View的测量值,决定ViewGroup的布局特性。
5.getMeasureWidth和getMeasureHeight方法必须在View的onMeasure方法执行完毕后才能获得具体的值,所以在Activity的onCreate,onResume方法无法准确的获取View的测量宽高,前一篇文章讲过因为View的measure流程和Activity的生命周期不是完全同步的,所以我们要在Activity获得View的测量宽高可用通过View.post方法,在View绘制完成回调,或者是在ViewTreeObserver 设置onGlobalLayoutListener监听的方法。