0%

View draw

前言

Android View 体系 draw 流程分析.

performTraversals()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void performTraversals() {
// 1 . TreeObserver.dispatchOnPreDraw() 接口返回 true
// 2. view 不可见
// 满足上述条件 cancelDraw 才会为 true
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

if (!cancelDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}

performDraw();
}

}

可以看到

performDraw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}

final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
mFullRedrawNeeded = false;

mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");


try {

boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
finishBLASTSync(true /* apply */);
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

}

draw(boolean fullRedrawNeeded)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return false;
}

mAttachInfo.mTreeObserver.dispatchOnDraw();

boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {

useAsyncReport = true;

mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {

if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}

return useAsyncReport;
}

可以看到在 draw 方法中,最终会走两条路,要么硬件绘制,要么软件绘制。我们看 drawSoftware

drawSoftware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

// Draw with software renderer.
final Canvas canvas;

try {
dirty.offset(-dirtyXOffset, -dirtyYOffset);
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;

canvas = mSurface.lockCanvas(dirty);

// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
} finally {
dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value.
}

try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}

// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}

dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;


canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);

mView.draw(canvas);

drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}

if (LOCAL_LOGV) {
Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
  • 可以看到平日我们在 onDraw 中使用的 Canvas 是通过 Surface 获得的。
  • 最终会调用 mView.draw(canvas) ,而通过之前的分析,我们知道这个 MView 就是 DecorView(FrameLayout)

当然,draw(Canvas canvas) 是 view 中定义的,FrameLayout 并没有覆写这个方法,因此我们直接看 View 中的 draw 方法。

View.draw(Canvas canva)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
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)
* 7. If necessary, draw the default focus highlight
*/

// Step 1, draw the background, if needed
int saveCount;

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
onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);

// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);

if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}

// we're done...
return;
}

// 如果 horizontalEdges 或 verticalEdges 为 true ,需要绘制 fading edges ,则需要下面的逻辑,但总的而实现不变,只是需要需要把 fading edges 的绘制按上述顺序插进来。
}

在 draw 方法里会按注释中的步骤

1
2
3
4
5
6
7
*      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)
* 7. If necessary, draw the default focus highlight

我们重点看一下 drawBackground, onDraw,dispatchDraw 的实现。

绘制步骤

drawBackground

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}

setBackgroundBounds();

// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.hasDisplayList()) {
setBackgroundRenderNodeProperties(renderNode);
((RecordingCanvas) canvas).drawRenderNode(renderNode);
return;
}
}

final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}

这里比较有意思的是,比较我们已经通过 drawsoftware 的方式来到了软件绘制,但是在这里依旧会尝试进行硬件绘制。因为我们知道在 Android 中可以从 Application、Activity、Window、View 的级别设置是否开启硬件加速,因此在某个条件下不支持硬件加速的视图在他内部可能就有某个 View 是支持硬件加速的。因此,可以看到从代码实现里,也是竭尽所能的使用硬件加速实现绘制,比较快嘛。

我们无论通过 android:background="#fff" 还是在代码里动态设置 background ,最终设置的 background 都换转换为 mBackground 的 Drawable。因此,drawBackground 就是 background 根据 layout 阶段确定的 mLeft/mRight/mTop/mBottom 等参数确定自己的边界(Bounds) ,然后调用 drawable.draw(canvas) 方法完成自己的绘制。

onDraw

1
2
3
4
5
6
7
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}

这个就不多说了,大家太熟悉了。

dispatchDraw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;

if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}

final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();

// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}

}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

dispatchDraw 顾名思义,就是分发 draw ,我们直接看 ViewGroup 中的实现。可以看到,在 View 中处理和 measure/layout 步骤是相似的,都是通过遍历子 view ,调用 drawChild 将 绘制再次分发到具体的子view上而已。

小结

总的来说 draw 的流程因为牵扯到硬件绘制、绘制顺序、动画、滑动等因素的影响,因此整体流程还是很复杂的。代码中的细节特别多,导致代码看起来很多。回到上层业务的使用,其实我们更多关心的会是 onDraw(Canvas canvas) 这个方法的实现,
Canvas 和 Paint 、Path 各类 API 结合后实现更高效和炫酷的动画。

Canvas

关于业务层在 onDraw 方法了,使用 Canvas 进行绘制操作的细节,参考扔物线的自定义 View - 绘制 相关的章节是最好的资料。

参考文档

扔物线-绘制

加个鸡腿呗.