源码版本Android 6.0 请参阅:http://androidxref.com/6.0.1_r10
本文目的是分析从Activity启动到走完绘制流程并显示在界面上的过程,在源码展示阶段为了使跟踪代码逻辑更清晰会省略掉一部分非主干的代码,具体详细代码请翻阅源码。
在上一篇文章Android UI绘制流程分析(二)中我们讲到了Activity的测绘流程是如何开始的,接下来我们开始分析UI绘制的三大流程,本文从measure流程开始: 在上一篇最后讲到performTraversals方法,该方法内部会分别调用
- performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)
- performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight)
- performDraw()
MeasureSpec
MeasureSpec是View类的一个内部类,我们先看看官方文档对MeasureSpec类的描述:
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.
意思其实是 MeasureSpec封装了父布局对子视图的布局要求,由尺寸和模式组成,每个MeasureSpec表示其对子View的宽度或高度要求。
其内部原理是一个32位的int值,高2位表示SpecMode,低30位表示SpecSize。 可以通过makeMeasureSpec来封装一个MeasureSpec,通过getSize()和getMode()来解封获取MeasureSpec内包含的尺寸大小和模式信息。解封操作是通过位运算来获取高2位(getMode())或者低30位(getSize())
#class in View&MeasureSpec
public static class MeasureSpec {
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;
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);
}
}
我们在使用View时是直接设置LayoutParams,但是在View测量的时候,系统会将该LayoutParams在结合父布局所给它的MeasureSpec来确定View的测量后的宽高然后生成自己的MeasureSpec,该MeasureSpec保存了View自身的宽高信息同时也包含了对子View的限制规则。
上述会有一个疑问,既然View的测量要根据自身的LayoutParams和父布局的MeasureSpec,那么顶层View(DecorView)哪里来的父布局MeasureSpec?
答:其实DecorView的测量和普通View是有区别的,DecorView的MeasureSpec是由屏幕尺寸和LayoutParams来决定的,并且DecorView的默认LayoutParams是match_parent(在初始化DecorView时可知),而普通View的MeasureSpec是由其LayoutParams和父布局的MeasureSpec决定的。
performTraversals开始测量
#class in ViewRootImpl
private void performTraversals() {
...
if (!mStopped) {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
...
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
上面说明DecorView的MeasureSpec生成规则是:
LayoutParams | MeasureSpec |
---|
MATCH_PARENT | MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); | WRAP_CONTENT | MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); | else(固定尺寸) | MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); |
生成之后将结果设置给childHeightMeasureSpec开始执行performMeasure进行测量:
#class in ViewRootImpl
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
#class in View
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
} else {
...
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
...
}
我们从最顶层View(DecorView)为例进行测量分析,具体继续往里测量子View是一个递归过程。 DecorView的真正测量逻辑在FrameLayout#onMeasure(widthMeasureSpec, heightMeasureSpec); 下面代码分析都以FrameLayout为例
#class in FrameLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
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) {
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());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
...省略前景、背景参与最大宽高计算的代码,这部分很简单,就是当前max与前景、背景取最大值
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
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();
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);
}
...高度同理
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
通过上述代码以及注释可以理解,在onMeasure中总共做了这几件事:
- 遍历子View,调用measureChildWithMargins方法对子View进行测量(具体测量规则稍后再说,此处暂时直接认为已经测量完毕)
- FrameLayout布局参数为wrap_content时,记录下所有宽或者高布局参数为match_parent的子View到mMatchParentChildren中以便后续进行重新测量
- 保存下测量结果,至此FrameLayout的宽高已经测量完毕,宽高已经确定
- 取出mMatchParentChildren中的所有View对其重新设置MeasureSpec然后执行测量流程,重设MeasureSpec的规则是(以width为例):
子View LayoutParams | MeasureSpec计算规则 |
---|
match_parent | MeasureSpec.makeMeasureSpec(FrameLayout宽度 - padding - margin,MeasureSpec.EXACTLY) | wrap_content | MeasureSpec.makeMeasureSpec(FrameLayout宽度 - padding - margin, MeasureSpec.AT_MOST) | 固定值(如50dp) | MeasureSpec.makeMeasureSpec(50dp, MeasureSpec.EXACTLY) |
- 使用新生成的MeasureSpec对子View进行重新测量,又是一个遍历的过程
整个流程通了之后,我们来看一下子View具体的测量规则:
#class in 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);
}
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) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
...
这种模式为系统使用的,我们一般用不到,不做分析
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
逻辑上感觉很绕,其实梳理之后很好理解,子View的大小根据其自身的LayoutParams和父给它的MeasureSpec来共同决定,大体规则就是遵循:
-
父大小精确: a.子LayoutParams大小设置准确值,结果就是:MeasureSpec(准确值,EXACTLY) b.子想撑满父,则把父剩余的空间全给它,结果就是:MeasureSpec(父SpecSize,EXACTLY) c.子也不确定它想自己控制大小,则父只能把最大剩余空间给它,只要它不超出就行,结果就是:MeasureSpec(父SpecSize,AT_MOST) -
父大小不确定: a.子LayoutParams大小设置准确值,结果就是:MeasureSpec(准确值,EXACTLY) b.子想撑满父,父把能给它的都给它,结果就是:MeasureSpec(父SpecSize,AT_MOST),后面会再测量一次 c.子想控制自己大小,父也是把能给它的都给它保证它不能超过父剩余的大小,结果就是:MeasureSpec(父SpecSize,AT_MOST)
| |
---|
父大小确定 | 子LayoutParams大小设置准确值:MeasureSpec(准确值,EXACTLY) | 父大小确定 | 子想撑满父,则把父剩余的空间全给它:MeasureSpec(父SpecSize,EXACTLY)) | 父大小确定 | 子想自己控制大小,则父只能把最大剩余大小给它要求子不能超过这个大小:MeasureSpec(父SpecSize,AT_MOST) | 父大小不确定 | 子LayoutParams大小设置准确值:MeasureSpec(准确值,EXACTLY) | 父大小不确定 | 子想撑满父,则把父剩余的空间全给它:MeasureSpec(父SpecSize,AT_MOST),后续会重新测量确定真正的大小 | 父大小不确定 | 子想自己控制大小,则父只能把最大剩余大小给它要求子不能超过这个大小:MeasureSpec(父SpecSize,AT_MOST),也会重新测量确定真正大小 |
参考网上的一张图片,过程就是: 接着看下测量完成之后需要将测量的结果设置给mMeasuredWidth和mMeasuredHeight,后续我们使用的时候调用View.getMeasureWidth()等方法时才能拿到具体的值。每个View在测量完成之后是通过 setMeasuredDimension(int measuredWidth, int measuredHeight) 方法来将结果保存给View的。
#class in View
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
至此测量流程已经结束
真正当我们自定义View时如果需要涉及到View的测量流程,在测量的最后必须将结果通过setMeasuredDimensionRaw设置给View,这样才是一个完整的自定义View的测量流程,否则你所测量出来的结果是不生效的。 大致可以这么做:
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int newWidthSpec = MeasureSpec.makeMeasureSpec(10, MeasureSpec.AT_MOST);
int newHeightSpec = MeasureSpec.makeMeasureSpec(20, MeasureSpec.AT_MOST);
super.onMeasure(newWidthSpec, newHeightSpec);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(10, 20);
}
}
|