一、需要了解的知识
- DecorView
DecroView 其实是一个 FrameLayout,它包含了一个垂直的 LinerLayout,这个 LinerLayout 包含手机通知栏、 titlebar 标题栏,下部分为内容栏也就是 Activity 通过 setContentView 设置的布局。
内容栏的 id 为 content,布局加到了 id 为 content 的 FrameLayout 中。
获取 content 内容栏:
ViewGroup content = findViewById(R.android.id.content);
获取设置的布局 View:
content.getChildAt(0);
ViewRoot 和 ViewRootImpl
ViewRoot 是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。然而 ViewRoot 不是 View,它的实现类 ViewRootImpl 跟 View 也没有什么直接的关系。详情可阅Android里那些令人费解的命名(一)ViewRootMeasureSpec
MeasureSpec 是 View 的一个 static 类,这个词像两个单词的组成,翻译为“测量规格”或“测量参数”。
MeasureSpec 代表一个 32 位的 int 值,它包含了 View 的大小(低30位代表 SpecSize)和测量模式(高2位代表 SpecMode)。MeasureSpec 将这两个值打包在了一起并提供了打包和解包的方法。
简单地说,MeasureSpec 代表了某个 View 的测量大小和显示模式。
MeasureSpec一共有三种模式:
UPSPECIFIED : 父容器不对 View 有任何限制,要多大给多大,一般用于表示一种数值未定的测量状态;
EXACTLY:父容器已经检测或计算出 View 所需的大小,这是一个比较精确的数值,一般最终 View 的大小就是这里指定的 SpecSize;
AT_MOST:父容器指定了一个可用大小,View 的大小不能超过这个值。
二、从源头寻找 View 绘制流程
2.1 DecorView 的创建和测量
1.创建 ViewRootImpl 对象并调用 performTraversals 开始绘制
ActivityThread 中,当 Activity 对象创建完毕,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象,并将 ViewRootImp 对象和 DecorView 建立关联。
// 创建 ViewRootImpl 对象
root = new ViewRootImpl(view.getContext(), display);
// 和 DecorView 建立关联
root.setView(view, wparams, panelParentView);
从 ViewRoot 的 performTraversals
方法开始,将 View 按照 measure、layout、和 draw 的过程绘制出来。performTraversals
方法会依次调用 performMeasure、performLayout、performDraw 来完成 DecroView 的绘制流程。
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// performMeasure 执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
WindowManager.LayoutParams lp = mWindowAttributes;
if (didLayout) {
// performLayout 执行 Layout,确定坐标
performLayout(lp, mWidth, mHeight);
...
}
...
// performDraw 执行绘制
performDraw();
...
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
/**
* 基于它的布局参数,计算出窗口中根视图的度量标准。
* 也就是根据屏幕宽高和 DecorView 布局参数来确定 DecorView 的 MeasureSpec
*
* @param windowSize
* 窗口的可用宽度或高度
*
* @param rootDimension
* 窗口的一维(宽度或高度)的布局参数。
*
* @return 用于度量根视图的度量指标。
*/
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 = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
break;
...
}
return measureSpec;
}
ViewGroup:performMeasure --> measure --> onMeasure 在这个过程中,onMeasure 会对所有子元素进行 measure 。
ViewGroup:performLayout --> layout --> onLayout 然后对所有子元素进行 layout
ViewGroup:performDraw --> draw --> onDraw 传递过程通过 dispatchDraw
来完成
2. 开始测量 DecorView
performMeasure 中其实调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
方法,mView 可以看作是上文 ViewRootImpl 对象设置进来的 DecorView 的实例。接下来看一下 DecorView 的继承关系:
DecorView extends FrameLayout extends ViewGroup extends View
DecorView、FrameLayout 还有 ViewGroup 最终都继承了 View类,FrameLayout 和 ViewGroup 都没有 measure 方法,所以 mView.measure 最终会调用 View 类的 measure 方法。
measure 方法是 View 类的一个 final 方法,不可重写。DecorView 调用了 measure 方法,接下来看一下 View 调用 measure 方法做了什么。
View 类 measure 方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec,heightMeasureSpec);
..
}
插播 1:View 中 onMeasure
所做的事情:
measure 方法调用了 onMeasure 方法,所以具体 View 的测量工作是在 onMeasure 方法中进行的:
View 类 onMeasure 方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension
方法可以简单理解为给 View 的 mMeasuredWidth,和 mMeasuredWidth 设置值,也就是确定 View 的测量宽高。这里的测量宽高是通过传递过来的父 View 的 MeasureSpec 和子 View 的默认属性共同决定的,getDefaultSize 方法会证明这一点。设置完成后就代表当前 View 的宽/高测量值确定了。
那么 View 的测量宽高是通过 getDefaultSize
方法返回的参数来确定具体的测量值。
getDefaultSize
方法传入两个参数
- 第一个参数为
getSuggestedMinimumWidth
方法的返回值:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
根据该方法的返回值来看,如果当前 View 没有设置背景,则返回的值为 View 的 android:minWidth 属性所设置的值,可以为0。如果指定了背景,则返回的值为 android:minWidth 属性值和背景的最小宽度两者中的最大值。对于高度,逻辑也是一样的,android:minHeight 属性和背景共同决定。
- 第二个参数为
measure
方法中传递过来的宽/高的 MeasureSpec 格式的测量值,是由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同决定的,后文再作分析。
View 类 getDefaultSize 方法
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = View.MeasureSpec.getMode(measureSpec);
int specSize = View.MeasureSpec.getSize(measureSpec);
// 根据父 View 传递来的 SpecMode 来决定返回 View 的测量值
switch (specMode) {
case View.MeasureSpec.UNSPECIFIED:
result = size;
break;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
上面已经分析过这两个参数,getDefaultSize
默认返回的是传递来的第一个参数,也就是由最小宽高和背景宽高共同决定的值。接下来进入 switch 判断,如果传递来的 SpecMode 为 UNSPECIFIED,则返回由 minHeight/minWidth 和背景共同决定的值。如果是 AT_MOST(规定上限)或 EXACTLY(精确值)模式,则返回由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同决定的测量参数的值,也就是第二个参数中的 SpecSize。
3. 递归测量 DecorView 的子 View,最后测量 DecorView
View 的 measure 方法会调用 onMeasure 来做具体的测量工作。DecorView 其实是一个 FrameLayout 且重写了 onMeasure 方法,所以接下来会进入 FrameLayout 的 onMeasure
方法。接着会调用父类 ViewGroup 的 measureChildWithMargins 方法来测量所有子 View。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 遍历子 View 测量它们的值
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 不为 GONE 的子 View 都会参与测量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 父类 ViewGroup 中的方法,用来测量所有子 View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
}
}
...
// 父类 View 中的方法,用来确定当前 View 的测量值(当前是 FrameLayout)
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}
插播 2:ViewGroup 中 measureChildWithMargins
所做的事情:
ViewGroup 类 measureChildWithMargins 方法源码
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// child.getLayoutParams()获取的是子 View 在 xml 中所设置的布局参数
final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
// 根据父 View 的测量参数 parentWidthMeasureSpec/parentHeightMeasureSpec
// 以及子 View 的布局参数 Margin、Padding 和 width/height 来确定子 View 的测量参数
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 是 ViewGroup 则会递归往下级 View 测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
简单理解:首先,拿到子 View 的 xml 设置的布局参数,父 View 的测量参数(MeasureSpec)是由父 View 的 onMeasure
传递过来的。接着,将这些参数传递到 getChildMeasureSpec
方法来计算出子 VIew 的测量参数。最后子 View 调用 measure
方法来继续测量,如果该子 View 是 ViewGroup,会继续调用 measureChildWithMargins
来递归测量下级 View,如果子 View 只剩一个单独的 View,则调用 measure
之后再调用
onMeasure
中的 setMeasuredDimension
来完成测量。
接下来看 getChildMeasureSpec
方法来计算出子 VIew 的测量参数的过程:
ViewGroup 类 getChildMeasureSpec 方法源码
/**
* @param spec 父 View 传递来的测量参数
* @param padding 上面方法传递来的父View的padding和子View的margin以及widthUsed(已使用的宽度/高度)
* lp 是子View的布局参数
* mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed
* @param childDimension 子View布局参数设定的宽或高
* @return 子View的布局参数
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = View.MeasureSpec.getMode(spec); // 父View的SpecMode
int specSize = View.MeasureSpec.getSize(spec); // 父View的SpecSize
// 具体大小为父View的大小 减去 父View的padding和子View的margin以及widthUsed
// 暂时称之为 View剩余大小
int size = Math.max(0, specSize - padding);
// 用来生成子View的测量参数
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父View的大小是EXACTLY模式
case View.MeasureSpec.EXACTLY:
if (childDimension >= 0) { // 子View布局参数宽/高大于等于0
resultSize = childDimension; // 子View的宽/高为子View设置的布局参数值
resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT
resultSize = size; // 让子View的数值为计算好的View剩余大小
resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) { // 子View是WRAP_CONTENT
resultSize = size; // 子View的数值为计算好的View剩余大小
resultMode = View.MeasureSpec.AT_MOST; // 子View的模式为AT_MOST,表示不能超过这个View剩余大小
}
break;
// 父View的大小是AT_MOST模式
case View.MeasureSpec.AT_MOST:
if (childDimension >= 0) { // 子View布局参数宽/高大于等于0
resultSize = childDimension; // 子View的宽/高为子View设置的布局参数值
resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT
resultSize = size; // 子View的数值为View剩余大小
resultMode = View.MeasureSpec.AT_MOST; // 因为父View是AT_MOST模式其数据不确定,
// 所以子View也是AT_MOST不能超过View剩余大小
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {// 子View是WRAP_CONTENT
// 子View想自适应,但它最大不能超过View剩余大小
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
}
break;
// 父View的大小是UNSPECIFIED模式
case View.MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {// 子View布局参数宽/高大于等于0
// 子View宽高是精确数值,父View不限制所以子View也不加限制
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY; //子View宽高是精确数值所以其模式也应为EXACTLY
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {// 子View是MATCH_PARENT
// 如果父View的大小未确定,返回0,否则返回View剩余大小
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED; // 父View大小无限制,子View也应是
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
// 判断模式与上一个类似
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED;
}
break;
}
// 根据上文子View的SpecMode和SpecSize来打包成一个MeasureSpec对象
return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
简单总结:
首先拿到父 View 的 specMode 和 specSize,specSize是父 View 的测量宽/高。使用 specSize 减去 父 View 的 padding 和子 View 的 margin 以及widthUsed 来计算出一个剩余大小,也就是子 View 能拿来用作显示的区域大小,暂且称之为 View 剩余大小。
然后根据父 View 的 specMode 和子 View 具体的布局参数来确定子 View 的 specMode 和 specSize:
- 父 View 是EXACTLY模式,说明父 View 的大小是精确的,一般来说,这个大小就是最终展示到屏幕上的大小。
- 如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。无论父 View 的大小与模式。
- 如果子 View 的大小为 "match_parent",当父 View 精确大小,那么子 View 的大小为计算好的 View 剩余大小,并且模式也是 EXACTLY。
- 如果子 View 的大小为 "wrap_content",当父 View 精确大小,子 View 再怎么自适应也不应超过父 View 剩余大小,所以 specSize 为 View 剩余大小,specMode 为 AT_MOST。
- 父 View 是AT_MOST模式,说明父 View 的大小不确定,但是会小于某个固定值。
- 同样,如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。无论父 View 的大小与模式。
- 如果子 View 的大小为 "match_parent",子 View 的模式为 AT_MOST,并且其最大值为 View 剩余大小。
- 逻辑同上,也应是 AT_MOST 模式并且最大值为 View 剩余大小。
- 父 View 是UNSPECIFIED模式,表面父 View 的大小没有约束,所以子 View 的大小也可以是没有限制大小的。
- 同样,如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。
- 如果子 View 的大小为 "match_parent",子 View 的大小理所应当也是无限制模式的,并且如果父View的大小未确定,返回0,否则返回View剩余大小。
- 逻辑同上。
(3)DecorView 作为 FrameLayout 调用 FrameLayout 的 onMeasure 方法,onMeasure 方法又调用了 ViewGroup 的 measureChildWithMargins 方法来测量所有子 View。子 View 测量完毕后,接着调用父类 View 的 setMeasuredDimension 方法,这个方法在上面 View 的测量流程中有提到过。
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(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
总之就是 DecorView 作为 FrameLayout 先测量子 View,然后再确定自己的测量值。关于子 View 是如何测量的,可以回去看第一条插播。
2.2 Layout 过程
DecroView 完成 performMeasure 中的 measure 过程后,就完成了 DecroView 的大小和绘制模式,接下来就会进行 Layout 过程来放置 DecroView 的具体位置。然后判断 didLayout (didLayout 由 Window 状态等参数决定),如果为 true 则执行 Layout 过程。
private void performTraversals() {
...
// performMeasure 执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
WindowManager.LayoutParams lp = mWindowAttributes;
if (didLayout) {
// performLayout 执行 Layout,确定坐标
performLayout(lp, mWidth, mHeight);
...
}
...
}
ViewRootImp 中 performLayout 方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
// 赋值给 host 并判断是否为 null
final View host = mView;
if (host == null) {
return;
}
...
// 进行具体的 layout 过程
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
由上面代码可知 DecorView 调用 layout 方法,这个方法被 ViewGroup 重写为 final 类型(说明所有继承 ViewGroup 的 View 的 layout 过程被 ViewGroup 统一管理),DecorView 是一个 FrameLayout 继承了 ViewGroup。所以会调用 ViewGroup 的 layout 方法。
ViewGroup layout 方法
@Override
public final void layout(int l, int t, int r, int b) {
// mSuppressLayout 判断当前布局是否被父 ViewGroup 进行增加或删除的动画效果
// mTransition 判断当前布局是否存在动画或动画正在改变布局
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// transition 动画正在运行设置为 true,等待动画完成重新调用 requestLayout() 重新请求 Layout 步骤
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
上述代码可以简单理解为 layout 之前先进行 Transition 动画的判定,如果有动画在执行则设置 mLayoutCalledWhileSuppressed 为 true 作为标记,等待动画完毕重新调用 layout。
如果Transition 没有在执行,则调用父类的 layout(l, t, r, b);
方法,也就是 View 类的 layout()
方法。
View layout 方法
public void layout(int l, int t, int r, int b) {
...
// 先标记旧的属性值
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 判断 LayoutMode 是否为 LAYOUT_MODE_OPTICAL_BOUNDS 模式,如果是则执行 setOpticalFrame。
// 不是则执行 setFrame,这个函数的作用为:判断当前 View 的属性是否发生变化,并返回是否有变化。
// 同时为当前 View 设置一个大小和位置。
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 判断当前 View 是否发生变化,来决定是否需要重新 layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// 判断是否在圆形可穿戴设备上绘制圆角
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
...
}
...
}
- 拿到上下左右的属性值并标记,然后判断属性值是否发生变化并更新设置。
- 如果 View 发生了变化则回调 onLayout() 来确定坐标,在初始情况下各个属性都是默认的。一旦子 View 完成 measure 过程,这些属性都会被赋值,所以会回调具体类的 onLayout() 方法。View 类的 onLayout() 方法是空实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
由于 DecorView 继承 FrameLayout 又继承 ViewGroup 又继承 View,所以倒着来看 :
ViewGroup 的 onLayout() 方法
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
简单粗暴的一个抽象方法,也就是说需要子类具体的去实现。
FrameLayout 的 onLayout() 方法
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
// 获取父 View 的相关属性
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 省略的是一些根据子 View 的 LayoutGravity 和父 View 的边界属性来重新计算的代码
...
// 计算完毕之后子 View 再递归进行 layout。根据具体的 View 回调相关的 onLayout 方法
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
- 到 layoutChildren 方法也是拿了一堆属性 ,然后遍历子 View 来balabala 进行计算(省略了)最终确定子 View 的左右上下边距再使用递归的方式来进行子 View 的 layout 方法。
- 进行 layout 会回调 onLayout 方法,不同的 View 会重写 onLayout 方法,具体可以去看不同 View 的源码。
-
onLayout(boolean changed, int left, int top, int right, int bottom)
方法中的各种属性在 measure 过程中计算好传递过来的,但是具体进行 layout 过程可能会根据子 View 的 LayoutGravity 模式来进行具体的更改。
至此,Layout 过程告一段落。
2.3 Draw 过程
performLayout 执行完毕之后会进行 performDraw 方法来进行绘制,
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// performMeasure 执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
WindowManager.LayoutParams lp = mWindowAttributes;
if (didLayout) {
// performLayout 执行 Layout,确定坐标
performLayout(lp, mWidth, mHeight);
...
}
...
// performDraw 执行绘制
performDraw();
...
}
performDraw()
方法经过层层调用会执行mView.draw(canvas);
方法,这里的 mView 依旧是 DecorView 的实例。因为View的draw 方法一般不去重写,官网文档也建议不要去重写 draw 方法,所以接下来进入 View 类的 draw 方法:
public void draw(Canvas canvas) {
...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
这块的注释写的非常清晰,一条条列出来了绘制步骤,其中第 2 步和第 5 步是在特点情况下才会进行的,所以分析时省略这两块的步骤:
- 绘制背景
private void drawBackground(Canvas canvas) {
Drawable final Drawable background = mBackground;
......
//mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false; rebuildOutline();
}
......
//调用Drawable的draw() 把背景图片画到画布上
background.draw(canvas);
......
}
- 如有必要,请保存画布图层以准备淡入淡出
- 绘制View的内容
View 的 onDraw() 和 ViewGroup 的 onDraw() 都是空实现,所以需要具体的子 View 去实现相应的绘制功能 - 绘制View的子View们
View 的dispatchDraw(canvas);
方法也是一个空实现,ViewGroup 实现了这个方法,在 ViewGroup 中该函数遍历所有子元素的 draw 方法,这样绘制事件就一层一层地传递了下去。
ViewGroup 的 dispatchDraw 方法
@Override
protected void dispatchDraw(Canvas canvas) {
...
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
......
}
drawChild 方法直接调用了 child.draw(canvas, this, drawingTime);
方法。又是在循环中,所以会调用所有 child View 的 draw 方法。
- 如有必要,绘制渐变边缘并恢复图层
- 绘制装饰(例如滚动条)
- 绘制默认焦点高亮
到这里 View 的绘制流程就基本结束了。
参考资料:
Android View的绘制流程
《Android 开发艺术探索》