Android View 的绘制流程 - 开篇 MeasureSpec
Android View 的绘制流程 01 - 前置流程
Android View 的绘制流程 02 - performMeasure
Android View 的绘制流程 03 - performLayout
Android View 的绘制流程 04 - performDraw
Android View 的绘制流程总结
在前两章分别学习了 measure 和 layout 的流程, 今天学习绘制流程的最后一步 draw.
4. ViewRootImpl.performTraversals()
ViewRootImpl.java 1576 行
private void performTraversals() {
...
if (...) {
...
if (...) {
if (...) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
layoutRequested = true;
}
}
} else {
...
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
//摆放
performLayout(lp, mWidth, mHeight);
...
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
...
//绘制-------------本章学习内容
performDraw();
} else {
...
}
...
}
从 performDraw 开始跟进.
进入到 performDraw()
4.1 ViewRootImpl.performDraw()
ViewRootImpl.java 2781行
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
又调用了 ViewRootImpl 的 draw 方法, 继续跟进
4.2 ViewRootImpl.draw()
ViewRootImpl.java 2843行
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
...
}
分析代码得知, 这里又调用了 ViewRootImpl 的 drawSofware 方法. 跟进.
4.3 ViewRootImpl.drawSofware ()
ViewRootImpl.java 3022行
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
...
mView.draw(canvas);
...
}
官方对这个方法的说明是
如果绘图成功,则为 true, 如果出错,则为 false.
这也就对应了步骤4.2 中的那个 if 判断了. 应该就是判断是否绘制成功的.
这里又看到了 mView 了. 还记得它是什么吗? 不记得的朋友可能要去前几章回忆一下了. 现在就要开始 DecorView 的绘制了.
那么我们进入 DecorView 的 draw 方法中, 看一下是怎么绘制的.
4.4 DecorView.draw()
DecorView.java 784行
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
看到代码非常简单,
先调用了父类( FrameLayout )的 draw 方法.
然后又绘制自己的菜单背景. (这个先不关注, 有需要的朋友自行进去看看)
但是进入 FrameLayout 中后发现并没有重写 draw 方法.
那就再去 FrameLayout 的父类 ViewGroup 看一下重写没有, 进去后, 发现还是没有. 那估计就只有在顶级父类 View 中了.
进入 View 中, 发现有 draw 方法. 我们就跳转到 View 中的 draw 方法
(View 的 draw 方法一般不去重写,官网文档也建议不要去重写 draw 方法)
4.5 View.draw() 返回值为 void
View.java 19088行
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
// 是否是实心的.
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
...
/*
* 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
...
if (!dirtyOpaque) {
drawBackground(canvas);
}
...
// skip step 2 & 5 if possible (common case)
...
// Step 3, draw the content
...
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
...
}
为什么会说明返回值为 void 呢, 因为后面还有有一个返回值为 booleaen 的 draw() 方法, 后面会有说明
官方对该方法的说明是
手动将该 View ( 及其所有的子 View ) 渲染到给定的画布上. 在调用该方法之前, 该 View 必须已经完成了完整的布局.
当实现一个 View 时, 应该实现 onDraw(android.graphics.Cavas) 而不是本方法. 如果确实需要重写该方法,请调用超类版本。
参数canvas:将view渲染到的画布。
解读: 其实无论当前 View 是 ViewGroup 还是单一 View, 都是需要实现这套流程的, 不同的是 ViewGroup 中, 实现了 dispatchDraw 方法, 而单一 View 中不需要实现这个方法. 自定义 View 一般都需要实现 onDraw 方法, 在其中绘制不同的样式.
从代码中看出做了不少事情, 还都设置好了步骤. 第2步和第5步在有需要的时候才会用到 (skip step 2 & 5 if possible (common case)
那么目前需要关心的就是下面3个步骤 (Step6 绘制前景和滚动条可以自行了解)
Step1: drawBackground(canvas) 如果有需要的话,画背景. 其实就是我们在布局文件中设置的 android:background 属性. 这是开始画的第一步.
Step3: onDraw(canvas) 画 View 的内容 (重点).
Step4: dispatchDraw(canvas) 画子 View. 这个方法用于帮助 ViewGroup 来递归画它的子 View
注: Step1 和 Step3 在调用之前, 都进行了一个判断, 也就是说 drawBackground 和 onDraw 方法不一定会被调用. 这个判断是什么意思呢?
dirtyOpaque 标志位的作用是判断当前 View 是否是透明的, 如果是透明的, 将不会执行 drawBackground 和 onDraw. 而是直接执行 dispatchDraw 去绘制内部的子 View. 也就是说 dirtyOpaque 标志位要为 false (透明, 非实心)才会调用这两个方法.
那么先跟进到 View 的 drawBackground 方法中.
4.6 View.drawBackground ()
View.java 19302行
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
//如果没有设置背景,就不进行绘制
if (background == null) {
return;
}
...
background.draw(canvas);
...
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
//调用 Drawable 的 draw 方法来进行背景的绘制
background.draw(canvas);
} else {
//平移画布
canvas.translate(scrollX, scrollY);
//调用 Drawable 的 draw 方法来进行背景的绘制
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
如果没有设置背景, 就直接 return 了. 设置了背景就会调用 Drawable.draw 方法来画. 这里就不再继续跟进去看, 我们需要把重点放到 onDraw, 和 dispatchDraw 这两个方法上面.
既然 onDraw 是画内容, 那么肯定是需要由具体的子类去实现的. 那么直接进入到 DecorView.onDraw
4.7 DecorView.onDraw()
DecorView.java 315行
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c,mWindow.mContentParent);
}
先调用了 super.onDraw方法, 然后后面又画了自己的东西.
跟踪发现 FrameLayout 也没有实现 onDraw 方法, ViewGroup 也没有. 等于说这里的 super.onDraw 调用的是 View.onDraw(),
现在进入到 View.onDraw()
4.8 View.onDraw()
View.java 16827行
protected void onDraw(Canvas canvas) {
}
View 的 onDraw(canvas) 是空方法. 因为每个 View 的内容是各不相同的,所以需要由子类去实现具体逻辑。
就像上面的 DecorView.onDraw() 方法一样, 实现了 onDraw() 方法, 然后画了菜单背景. DecorView 到这里就画完自己了. 接着就开始调用 dispatchDraw() 方法画子 View 了.
那么先看下 View.dispatchDraw() 中做了什么, 进入后发现是一个空方法. 什么事情都没做. 就像 View.onDraw 一样. 为什么呢?
因为对于叶子 View 来说, 该方法没有什么意义, 它自己就是叶子 View 了, 没有子 View 需要画了. 但是对于 ViewGroup 来说, 就需要重写该方法来画它的子 View.
那么进入 DecorView 中查看 dispatchDraw() 方法. 发现没有重写这个方法, FrameLayout 也没有重写, 一直跟到 ViewGroup 中才找到了这个方法. 所以当 DecorView 执行完 onDraw() 方法后, 流程就会切到 ViewGroup 中的 dispatchDraw() 方法中.
( 在源码中发现, 像平时常用的 LinearLayout, FrameLayout, RelativeLayout 等常用的布局控件, 都没有再重写该方法. )
4.9 ViewGroup.dispatchDraw()
ViewGroup.java 3927行
@Override
protected void dispatchDraw(Canvas canvas) {
...
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
...
for (int i = 0; i < childrenCount; i++) {
...
if (...) {
more |= drawChild(canvas, child, drawingTime);
}
...
}
...
}
又看到了 递归子 View 的循环. 在循环中调用了 ViewGroup 中的 drawChild 方法, 并传入了子 View 对象.
( DecorView 的第一个子 VIew 是 LinearLayout. ) 现在进入 ViewGroup.drawChild() 方法
4.10 ViewGroup.drawChild()
ViewGroup.java 4213行
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
先看一下官方对该方法的说明
绘制当前 ViewGroup 的一个子 View. 该方法负责在正确的状态下获取画布. 这包括了裁剪, 移动, 以便子 View 的滚动原点在0.0处, 以及提供任何动画转换。
参数1: 画布
参数2: 需要绘制的子 View
参数3: 绘制发生的时间点.
注意: 这里调用的 draw() 方法, 是带有返回值的方法, 上面我们看到调用的draw() 方法都是没有返回值的.
DecorView 的子 View 是 LinearLayout , 进入 LinearLayout 后发现没有重写这个方法, ViewGroup 中也没有重写, 那么肯定还是在View 中了. 进入到 View.Draw() 返回值为 boolean 的方法中.
4.11 View.draw() 返回值为 boolean.
View.java 18760行
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
//跳过当前 View 的绘制, 直接绘制子 View.
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
...
return more;
}
官方对该方法的注释说明为:
该方法被 ViewGroup.drawChild() 方法调用. 为每个子 View 绘制自己.
是 View 专门根据图层类型(cpu 还是 gpu)进行渲染和硬件加速.
代码中省略的部分意思大概为:
当 ViewGroup 向下分发绘制 子View 的时候, 会根据是否开启硬件加速和 View 的绘制类型来判断当前 View 是用 CPU 绘制还是 GPU 绘制, GPU 的主要作用就是渲染图形, 绘制图形的速度会高于 CPU, 但是 GPU 会比 CPU 更加耗电.
注: 从代码中看到, 会先有一个判断, 这个判断和上面 void draw() 方法中的类似, 都和 mPrivateFlags 有关.
如果 mPrivateFlags 包含 PFLAG_SKIP_DRAW 那么会直接调用 dispatchDraw 绘制当前 View 的子 View.
如果不包含, 则会调用当前 View 中返回值值为 void 的 draw() 方法. 继续执行 void draw() 方法中的几个步骤
至于是否包含 PFLAG_SKIP_DRAW, 这里不再过多描述, 请参考 你真的了解Android ViewGroup的draw和onDraw的调用时机吗 这篇文章, 介绍的非常清晰.
到此差不多结束了View 的绘制流程. 下面用一张图来描述下 draw 的过程.