view的大三流程开始之地在performTraversals过程中,而measure是三个流程中较为复杂的过程。而measure的开始地方在performTraversals中的代码片段:
.....
//mStopped==true,该窗口activity处于停止状态
//mReportNextDraw,Window上报下一次绘制
if (!mStopped || mReportNextDraw) {
//触摸模式发生了变化,且检测焦点的控件发生了变化
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//1. 焦点控件发生变化
//2. 窗口宽高测量值 != WMS计算的mWinFrame宽高
//3. contentInsetsChanged==true,边衬区域发生变化
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//------开始执行测量操作--------
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
可见在measure流程开始之前通过getRootMeasureSpec()获取根布局的MeasureSpec并传入performMeasure中开始measure流程。
MeasureSpec
MeasureSpec代表一个32位int值,高2位为SpecMode,低30位为SpecSize。在View中有MeasureSpec内部类定义对MeasureSpec的相关处理及常量定义。
/** @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;
// Creates a measure specification based on the supplied 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);
}
}
SpecMode有三种情况:
- UNSPECIFIED:父布局对View没有任何约束,View可以是任何它想要的尺寸。
- EXACTLY:父布局精确设定了View的大小,无论子布局想要多大的都将得到父布局给予的精确边界。这种情况实际上就对应的是我们在xml文件或者LayoutParams中设置的xxdp/px或者MATH_PARENT这两种情况,也就是精确的给予了布局边界。
- AT_MOST:在确定的尺寸(父布局指定的SpecSize)内,View可以尽可能的大但不得超出SpecSize大小。这种情况对应的就是我们在xml文件或者LayoutParams中设置的WRAP_CONTENT,子View可以随着内容的增加而申请更大的尺寸,但是不能超过指定的SpecSize。
在measure过程中,对于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的(还与View的margin和padding有关);而对于DecorView来说,其MeasureSpec由它自身的LayoutParams决定。
DecorView的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
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;
}
因为DectorView的MeasureSpec只与自身LayoutParams有关。可以分为三种情况:
- LayoutParams.MATH_PARENT:MeasureSpec.EXACTLY模式,强制DectorView大小与window大小一致
- LayoutParams.WRAP_CONTENT:MeasureSpec.AT_MOST模式,且不可超过window大小
- default:MeasureSpec.EXACTLY模式,指定为DectorView的lp大小
View的MeasureSpec
View的mesure过程由ViewGroup传递。ViewGroup则相对复杂一些。
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);
}
这就与上文说到的对于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的(还与View的margin和padding有关)。
子View的MeasureSpec创建:与父容器的MeasureSpec和自身的LayoutParams有关,此外还和View的margin及padding有关。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//子View可用空间
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: //父布局为EXACTLY模式,对应match_parent及dp/px
if ( >= 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
case MeasureSpec.AT_MOST: //父布局为AT_MOST模式,
if (childDimension >= 0) {
// Child wants a specific size... so be it
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.
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.
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
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
最终可以整理出一个表格,引用《Android开发艺术探索》中的表图。
MeasureSpec
稍微整理则是:
- 当子view指定dp/px :使用EXACTLY模式,并遵循LayoutParams大小
- 当子view宽/高为match_parent :
- 父布局为EXACTLY,则子view为EXACTLY,且大小为parent可用大小
- 父布局为AT_MOST,则子view为AT_MOST,且大小为parent可用大小
- 当子view宽/高为wrap_content:使用AT_MOST,且大小为parent可用大小
而UNSPECIFIED一般我们在开发时不会用到故而不做分析。
measure
因为view通过measure即可完成测量过程,而ViewGroup还需要遍历调用子view的measure方法。所以需要分别讨论。
View的measure
View的measure由measure方法完成,而measure方法为final类型子类不可重写。在measure方法中会调用onMeasure方法,且具体测量过程都是在该方法中完成。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//1.AT_MOST、EXACTLY情况下返回MeasureSpec中的Size
//2.UNSPECIFIED使用getSuggestedMinimumXX的返回值
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;
}
//1.无背景:使用android:minWidth(mMinWidth)的值(可为0)
//2.有背景:取背景大小或mMinWidth中的 最大值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
一般我们只会使用到AT_MOST、EXACTLY模式,但是需要注意的是当为AT_MOST下默认使用的是parentSize,即相当于match_parent,所以自定义view要实现wrap_content时需要自己实现。
而使用UNSPECIFIED,则是
- 有背景,取背景大小和mMinWidth/mMinHeight 中最大值
- 无背景,取mMinWidth/mMinHeight
这样子就确定了view的测量值,而view最终大小则由layout过程确定,一般情况两者一致。
ViewGroup的measure
因为ViewGroup除了完成自己的measure还需要遍历子view的measure过程。而ViewGroup是个抽象类,提供了measureChlidren的方法,而对应onMeasure则需要对应实现的ViewGroup子类去实现了。
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);//对每个children进行measure
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//getChildMeasureSpec可看上文的MeasureSpec解析
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
看见measureChildren时对Visibility==GONE的view不进行measure操作。对于不同ViewGroup得分析网上已经有很多分析了。这里稍微总结一下就是,
- LinearLayout(vertical):先测量所有子View大小,根据子View总高度和自身MeasureSpec得出剩余空间去分配使用weiget的view的大小,最后测量自身大小。
- RelativeLayout:根据依赖分别排序垂直和水平方向的view,并针对垂直和水平方向的view进行measure(一次水平一次垂直),最后测量自身大小
- FrameLayout:测量子View,测量自身,如果自身为非精准模式而子View为match_parent则这些子view需要再次测量。
测量完是不一定能拿到View的对应测量大小的,因为系统在某些情况下可能进行多次测量测能确定宽高。所以自定义view时最好在onLayout去获取测量大小。外部获取view大小可以通过:
- Activity/View#onWindowFocusChanged:这时view已经初始化完成,但该方法可能多次被调用(获取/失去焦点都会被调用)
- View#post:在Android Handler原理源码浅析知道,当执行runnable时view已经初始化完成(MainLooper已经完成绘制去除了消息屏障)
- ViewTreeObserver:通过OnGlobalLayoutListener接口回调onGlobalLayout时view树可见性已经发生变化,这时去获取view的宽高则可以(这个接口可能多次调用,回调后需取消监听)
LinearLayout Measure过程(Vertical)
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
//null 或者 GONE 跳过measure
......
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
//精准模式直接记录children高度
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
//measure子view高度
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
//记录children高度
.....
}
.....
}
......
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//测量自身高度,方便计算是否还是有剩余高度
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
//当有还有剩余空间,则针对对应weight分配空间
.....
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//设置自身测量大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
RelativeLayout onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
sortChildren();//根据依赖图分别按垂直和水平排序子view
}
...
//根据水平方向依赖关系,measure子view
View[] views = mSortedHorizontalChildren;
int count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
//根据垂直方向依赖关系,measure子view
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
....
}
}
//测量自身
....
setMeasuredDimension(width, height);
}
FrameLayout onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//是否需要测量match_parent的子view,当为精准模式不需要测量match_parent的子view
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) {
//测量ziview
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
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());
//将match_parent子view添加进列表,需要再次测量
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
.....
//测量自身大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
//自身非精准模式,而子view为match_parent需要重新测量这些view
if (count > 1) {
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) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
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);
}
}
}
关于ConstraintLayout的measure解析,日后补上
参考:
- 《Android开发艺术探索》
- View绘制流程及源码解析(二)——onMeasure()流程分析
网友评论