1 Activity 与 Window、PhoneWindow、DecorView 之间的关系
- 每一个Activity都持有一个Window对象
- Window是一个抽象类,提供了绘制窗口的一组通用API。可以理解为一个显示View的载体。
- PhoneWindow是Window唯一的实现类。Activity 中的Window 实例就是一个 PhoneWindow 对象。
- DecorView类是PhoneWindow类的内部类,每个PhoneWindow中持有一个DecorView对象,Activity中View相关的操作大都是通过DecorView完成的(DecorView是Activity界面的根View)。
- DecorView继承FrameLayout(DecorView→FrameLayout→ViewGroup→View),通常DecorView包含一个竖直方向的LinearLayout,LinearLayout上面TitleView(标题栏),下面是ContentView(内容栏)。DecorView会获取Activity的setContentView()传递过来的布局id,通过inflater(布局填充器),将布局资源id加载成一个View,并添加到内容栏(R.android.id.content)。
2 View绘制的来龙去脉
- 首先我们可以把所有需要绘制的内容看成一颗View树,一个树本质上就是一个根节点,View树的根节点不是View就是ViewGroup,ViewGroup继承于View,所以本质上这个根节点就是View。因此归结下来,我们要绘制的就是一个最外层的根View,也就是整颗View树的根节点。
- Activity的根View就是DecorView,在Android层面来讲,View的显示是依赖与Window窗口的,因此DecorView的显示就依赖于Activity 中的的PhoneWindow对象。
- 由于一个Window对象就对应了一个View对象,两者是一一对应关系,所以实际上在创建Window的过程中就会完成View的添加与显示。
- 那Activity是怎么创建自己Window对象的呢?实际上Window对象是由远程的WMS系统服务实现创建的,Activity作为本地应用只能通过WindowManager对象通过IPC(跨进程通信)的方式进行创建Window对象。如何获取WindowManager对象呢,Context提供一些方法可以获取WindowManager对象。
- 具体方法调用流程:
Activity:handleResumeActivity(该方法内使用Context.getWindowManager创建WindowManager对象)→
WindowManager:addView(该方法内WindowManager委托代理给一个WindowManagerGLobal对象)→
WindowManagerGLobal:addView(该方法内创建了ViewRootImpl对象)→
ViewRootImpl:setView→requestLayout→scheduleTraversals→doTraversal→performTraversals(最终到达绘制的入口)
- 其中从WindowManager.addView开始就是Activity创建Window的过程,最终在ViewRootImpl对象的performTraversals中完成View的绘制(一个Window对象对应了一个ViewViewRootImpl对象也对应了一个View对象,即DecorView)
- performTraversals()是绘制的入口,它依次调用performMeasure()、performLayout()和performDraw()三个方法,三个方法内部分别调用了DecorView的measure()、layout()和draw方法。
结论:追根溯源后发现,DecorView的绘制就是调用了自己的measure()、layout()和draw()这三个方法,因此View的绘制可以概括成三个流程。
3 View绘制的三个流程
简单概括View绘制的三个流程,分别对应了View的三个方法: ①measure测量:测量出整个View树所有View的宽高,给出每个View的“测量宽高”(会先判断是否需要重新计算) ②layout布局:确定整个View树所有View的“最终宽高”和四个顶点的位置(会先判断是否需要重新计算) ③draw绘制:绘制整个View树的所有View(会先判断是否需要重新绘制)
3.1 measure测量
在此之前,我们需要了解MeasureSpec这个类,它在测量中取到了巨大作用,经常会作为参数传入。
- MeasureSpec是View类的一个静态内部类,MeasureSpec表示的是一个 32 位的整数值,它的高 2 位表示测量模式SpecMode,低 30 位表示这种测量模式下的规格大小SpecSize,总共有三种测量模式。(MeasureSpec提供了一些将一个32位整数打包或解包成<mode,size>的二元组的API,使得这些特殊的32位整数有了特定的含义,从抽象意义上可以说它封装了从父级传递到子级的布局约束),三种SpecMode:
- UNSPECIFIED(00):不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
- EXACTLY(01):精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
- AT_MOST(10):最大值模式,当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。
三种模式暂时不理解也没关系,我们先简单了解完MeasureSpec,就可以从测量的入口performMeasure()开始了:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
//Activity中的mView就是DecorView
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
测量从根View的measure方法开始,但实际的测量工作是在measure中调用的onMeasure方法中执行,可以看到performMeasure传入两个来自父布局的宽和高的布局约束,这两个布局约束会传入根View的measure方法然后再传入onMeasure方法。因此我们知道了,测量会接受来自父布局的宽高的约束。
View.measure方法是一个final方法,无法被重写,而onMeasure即是实际测量的方法也是可以被重写的方法,另外View绘制的另外两个流程也类似,因此我们自定义View通常就是重写onMeasure、onLayout和onDraw这三个方法
在看onMeasure方法前,你可能会好奇,那performMeasure传进的这两个布局约束是哪来的呢,根View的父级传来的吗?那根View的父级是什么?这就得回到ViewRootImpl的performTraversals方法看看了。
private void performTraversals() {
...
//这里获取的MeasureSpec就是传递给DecorView的布局约束
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
再来看看getRootMeasureSpec方法的参数名,以及对rootDimension的分支判断。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//最终会走到这个分支
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
break;
...
}
return measureSpec;
}
相信你能推断出来了,Activity中lp.width=lp.height=ViewGroup.LayoutParams.MATCH_PARENT,mWidth是屏幕宽度,mHeight是屏幕高度。在getRootMeasureSpec方法中调用了View.MeasureSpec.makeMeasureSpec方法完成mode和size打包成一个32位整数,它就是传递给DecorView初始的两个布局约束(精确模式的屏宽和精确模式的屏高)。
终于可以安心进入onMeasure方法看看了,但是要注意,DecorView→FrameLayout→ViewGroup→View,所以调用的是哪一个onMeasure方法呢?实际上DecorView和FrameLayout都重写了onMeasure方法,**ViewGroup虽然自身没有重写onMeasure方法,ViewGroup内部提供了很多关于递归测量子View的方法,继承了ViewGroup的类都会重写onMeasure方法,在onMeasure方法中调用ViewGroup提供的方法来完成测量工作。**由于继承了ViewGroup的类的onMeasure方法核心思想都差不多,这里就拿AbsoluteLayout(ViewGroup子类)的源码举例。
显然ViewGroup和View的测量工作是不一样的,View只用测量自身。ViewGroup测量自身的同时还要让自己所有的子View(或ViewGroup)完成他们的测量。因此整颗View树的测量像是一个递归的过程。
来看看AbsoluteLayout重写过的onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
// 测量所有子View的入口
measureChildren(widthMeasureSpec, heightMeasureSpec);
...
//所有子View都测量完毕后,会在resolveSizeAndState测量ViewGroup自身
//调用setMeasuredDimension设置测量结果,一旦调用该方法意味着这个View的测量结束
//PS:setMeasuredDimension方法必须在onMeasure方法中调用,不然会抛异常。
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0)
);
}
大部分ViewGroup子类最终都会调用resolveSizeAndState方法来测量自身的,我们晚点再来看看这个方法,先看看它是如何让完成每个子View的测量
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) {
//测量每个子View的入口
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
//子View获取自身的LayoutParams
final LayoutParams lp = child.getLayoutParams();
//获取父级对每个子View的布局约束(或者说:计算传入每个子View的measure方法的MeasureSpec)
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);
//子View调用自身的measure完成自身的测量工作
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
核心部分的来了,让我们来看看getChildMeasureSpec方法是如何使用父级的布局约束和子View的LayoutParams来获得父级对每个子View的布局约束(或者说:使用传入ViewGroup的MeasureSpec和每个子View的LayoutParams计算出传入每个子View的MeasureSpec)
在FrameLayout中:onMeasure→measureChildWithMargins→getChildMeasureSpec
再次说明:measureChildren、measureChild、measureChildWithMargins、getChildMeasureSpec都是ViewGroup中的方法,ViewGroup不同的子类很多都是重写onMeasure方法,然后调用ViewGroup提供的这些方法。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//这里的spec是父布局对ViewGroup的布局约束,我们要计算获得父级对子View的布局约束
//获取spec的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//获取减去父级padding后的Parent剩余的大小,不足0取0
int size = Math.max(0, specSize - padding);
//定义返回值
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//当父布局约束是精确模式时
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {//LayoutParams中的常量都是<0的
//当前子View有确切大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//当前子View布局是MATCH_PARENT
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//当前子View布局是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:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//返回父级对子View的布局约束
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
如果子View是ViewGroup的话就会和上面一样一直递归调用,直到View树的叶子结点,也就是View。让我们看看View的onMeasure方法拿到父级对自己的布局约束会怎么完成测量。
View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
一样setMeasuredDimension是用来设置测量结果的,我们先看看简单的getSuggestedMinimumWidth()方法
//返回android:minWidth属性的值与背景图片宽度两者中较大的值,如果没有设置背景的话直接返回mMinWidth(默认0)
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
mBackgroud是Drawable对象。这里就不深入Drawable了,来看getDefaultSize。
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:
//UNSPECIFIED模式返回getSuggestedMinimumWidth()方法的返回值
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
//另外两种模式都返回specSize
result = specSize;
break;
}
//返回View的测量大小
return result;
}
可以看到,两种模式都是返回父布局的大小,也就是自定义View的话即使你是wrap_content,也会按match_parent处理。因此根据需要可以在自定义View中增加对AT_MOST这种情况的处理。
最后我们再回过头来看看当子View完成测量后,ViewGroup是如何完成对自身的测量的。即前面的resolveSizeAndState方法。
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
//解包父级对ViewGroup的约束条件
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
//定义返回值
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
//父级wrap_content情况下
if (specSize < size) {
//父级约束小于ViewGroup想要的大小,取父级约束的大小
//多出来的一些位可以设置一些标志
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
//取ViewGroup想要的大小
result = size;
}
break;
case MeasureSpec.EXACTLY:
//父级约束有确切大小情况下,ViewGroup取父级约束的大小
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
//返回ViewGroup的测量大小
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
测量过程总结:
-
ViewGroup接受来自父级的布局约束(即ViewGroup的MeasureSpec)让子View们计算自己的MeasureSpec(根据ViewGroup的MeasureSpec和自身的LayoutParams),然后再递归调用测量的方法。
-
直到传递到View树的叶结点,在View.onMeasure方法中,根据自己的MeasureSpec得到自己的测量大小
-
待ViewGroup的所有子View都得到自己的测量大小后,ViewGroup再计算自己的测量大小。
3.2 layout布局
layout流程的基本思想和measure差不多,也是由根View开始,递归地完成整个View树的布局工作。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
...
final View host = mView;
if (host == null) {
return;
}
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
View的layout方法时可以被重写,ViewGroup简单封装了一点点,基本还是调用View的layout方法,DecorView和FrameLayout都没有重写。因此主要还是调用了View的layout方法。
public void layout(int l, int t, int r, int b) {
// l为本View左边缘与父View左边缘的距离
// t为本View上边缘与父View上边缘的距离
// r为本View右边缘与父View左边缘的距离
// b为本View下边缘与父View上边缘的距离
...
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
...
}
setFrame()方法会设置View的mLeft、mTop、mRight和mBottom四个参数,分别描述了View相对父View的位置,setFrame()方法还会判断是否需要重新布局。然后进入onLayout方法。
View的onLayout方法是一个空实现,ViewGroup 则是一个抽象方法,要求其子类必须重写onLayout函数。ViewGroup的之类重写的onLayout的思路都差不多,递归遍历子View的onLayout方法。
//基本逻辑
protected void onLayout(boolean changed,int l, int t, int r, int b) {
int childCount = getChildCount();
for (int i = 0;i < childCount; i++){
View child = getChildAt(i);
child.layout(l, t, r, b);
}
}
简单看一下FrameLayout的实现:
@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();
//父级的上下左右
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//计算每个子View相对于父View的位置
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//子View测量的宽高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
//结合特定布局的属性,像这里FrameLayout就结合layout_gravity属性等。
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//计算完后,交给子View的layout方法中的setFrame方法来设置布局,最终完成布局流程
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
3.3 draw绘制
从performDraw()开始一路跳转
private void performDraw() {
...
mFullRedrawNeeded = false;
boolean canUseAsync = draw(fullRedrawNeeded);
...
}
private boolean draw(boolean fullRedrawNeeded) {
...
drawSoftware(surface, mAttachInfo, xOffset, yOffset,scalingRequired, dirty, surfaceInsets))
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
mView.draw(canvas);//调用DecorView的draw方法
...
}
调用DecorView的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
...
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
...
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
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);
...
return;
}
...
//这后面是一个更完整的流程,但是一般不会走到这。
}
一路跳转到draw(Canvas canvas)方法才开始绘制,绘制一共有6个步骤(注释是六步,其实现在有七步),一般情况下会跳过2和5。我们来看剩下的5个步骤。
第一步:绘制背景
//调用Drawable的draw() 把背景图片画到画布上
background.draw(canvas);
第二步 保存canvas层
第三步:绘制View自身的内容
// Step 3, draw the content
onDraw(canvas);
View.onDraw(canvas) 方法是个空实现,ViewGroup也没有重写。通常我们自定义View就可以重写onDraw方法来绘制自己想要的内容。
第四步 对所有子View进行绘制
// Step 4, draw the children
dispatchDraw(canvas);
View没有子View,因此其dispatchDraw(canvas)方法是空实现,ViewGroup中则重写了此方法。具体的代码逻辑就是遍历子View,调用子View.draw()方法。
第五步 如果需要,绘制View的褪色边缘
第六步 绘制装饰,比如对View的滚动条进行绘制
调用View的onDrawForeground绘制装饰