当 layout 完成后,就进入到 draw 阶段了,在这个阶段,会根据 layout 中确定的各个 view 的位置将它们画出来。
1、从 performDraw 方法到 draw 方法
draw 过程从 ViewRootImpl 类的 performTraverserals() 方法中调用 performDraw() 方法开始。如下所示:
// ViewRootImpl.java
private void performDraw() {
......
boolean canUseAsync = draw(fullRedrawNeeded);
......
}
private boolean draw(boolean fullRedrawNeeded) {
......
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
......
}
private boolean drawSoftware(......){
......
mView.draw(canvas);
......
}
经过一系列调用后,最后调用了 mView 的 draw 方法,从前面的内容可以知道,这里的 mView 为 DecorView 的实例,所以进入到 DecorView 的 draw 方法中。
2、DecorView 完成 draw 流程
下面便是 DecorView 类中的 draw 方法的代码。
// DecorView.java
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
可以看到,draw 方法中主要还是调用了父类的 draw 方法,然后还调用了 mMenuBackground 的 draw 方法进行了菜单背景的绘制。由于 FrameLayout 和 ViewGroup 都没有重写 draw 方法,所以就直接进入到了 View 类中的 draw 方法。
下面便是 View 类中的 draw 方法。
// View.java
/**
* 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.
*/
@CallSuper
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)
*/
// 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 (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int topSaveCount = -1;
int bottomSaveCount = -1;
int leftSaveCount = -1;
int rightSaveCount = -1;
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
// must be restored in the reverse order that they were saved
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(rightSaveCount, p);
} else {
canvas.drawRect(right - length, top, right, bottom, p);
}
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(leftSaveCount, p);
} else {
canvas.drawRect(left, top, left + length, bottom, p);
}
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(bottomSaveCount, p);
} else {
canvas.drawRect(left, bottom - length, right, bottom, p);
}
}
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(topSaveCount, p);
} else {
canvas.drawRect(left, top, right, top + length, p);
}
}
canvas.restoreToCount(saveCount);
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);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
该方法负责手动渲染该 view(以及它的所有子 view )到给定的画布上。在该方法调用之前,该 view 必须已经完成了完整的布局过程。如果需要自己实现一个 view 时,建议实现 onDraw 方法而不是重写 draw 方法。如果确实需要重写 draw 方法,需要调用超类版本。
view 需要渲染到 canvas 上,而参数 canvas 就是该 view 需要渲染的目标画布。
源码很长,但是注释描述了 draw 阶段完成的几个步骤:
1)绘制背景。对应我们在 xml 布局文件中设置的“android:background”属性,这是整个绘制过程的第一步。
2)必要情况下,保存当前 canvas 的图层来为后续实现渐变效果做准备。这里可以不必过多关心,暂时跳过。
3)绘制内容。这里调用了 onDraw 方法,DecorView 重写了该方法,所以是进入到 DecorView 的 onDraw 方法。。
4)绘制子 View。这里通过调用 dispatchDraw 方法来帮助继承自 ViewGroup 的容器布局来递归绘制它的子 view。
5)必要情况下,绘制渐变效果的边缘并恢复 canvas 的图层。这里可以不必过多关心,暂时跳过。
6)绘制装饰效果。里指画滚动条和前景。其实平时的每一个 view 都有滚动条,只是没有显示而已。
7)绘制默认焦点高亮效果。
下面便是 DecorView 的 onDraw 方法的代码。
// DecorView.java
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
mStatusColorViewState.view, mNavigationColorViewState.view);
}
可以看到,这里主要还是调用了父类的 onDraw 方法,然后调用了一个回调方法,该方法和状态栏、导航栏相关。由于 FrameLayout 和 ViewGroup 并没有重写 onDraw 方法,所以这里进入到了 View 的 onDraw 方法。
下面是 View 的 onDraw 方法。
// View.java
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
View 中的该方法是一个空方法,所以对于 DecorView 来说,这个方法什么也没有做。但是从注释中可以看到,我们通过实现该方法来实现具体的绘制工作。也就是说,具体的 view 需要重写该方法,来画自己想展示的东西,如文字,线条等。
将视线回到 View 的 draw 方法中,去看一下 dispatchDraw 方法。
// View.java
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
DecorView 和其父类 FrameLayout 中都没有重写该方法,该方法在 ViewGroup 中重写,而 View 中的 dispatchDraw 方法是一个空方法。该方法在 draw 方法中被调用,主要负责绘制子 View,View 中是一个空方法,所以实际上对于叶子 View 来说没有什么意义,因为它没有子 View 需要绘制。最终这里是进入到了 ViewGroup 中的 dispatchDraw 方法。
ViewGroup 中的 dispatchDraw 方法主要结构如下。
// ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
more |= drawChild(canvas, child, drawingTime);
......
}
......
}
可以发现,这里其实就是遍历 ViewGroup 中的所有子 View,并调用了 drawChild 方法。
ViewGroup 中的 drawChild 方法。
// ViewGroup.java
/**
* Draw one child of this View Group. This method is responsible for getting
* the canvas in the right state. This includes clipping, translating so
* that the child's scrolled origin is at 0, 0, and applying any animation
* transformations.
*
* @param canvas The canvas on which to draw the child
* @param child Who to draw
* @param drawingTime The time at which draw is occurring
* @return True if an invalidate() was issued
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
该方法负责绘制容器布局中的某一个子 View,获得正确状态的 canvas,包括裁剪、平移,以便使子 view 的滚动原点为0,0,并提供任何动画转换。
继续跟踪源码,进入到 View 的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法。
// View.java
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
draw(canvas);
......
}
该方法的主要工作是让每一个子 View 来绘制自己,最终走到子 view 的 draw 方法,形成递归,直到绘制完成完整的 DecorView 树。