Draw过程

2018-08-07  本文已影响58人  Utte

一、调用流程

1. ViewRootImpl # performTraversals()

performTraversals()调用了performDraw(),三大流程的起点都是performTraversals()。

// 没有取消draw也没有创建新的平面 第一次traversals时newSurface为true
if (!cancelDraw && !newSurface) {
    // ...
    // 开始draw流程
    performDraw();
} else {
    if (isViewVisible) {
        // Try again
        // 如果是可见的, 就再调用一次traversals
        scheduleTraversals();
        // ...
    }
    // ...
}

2. ViewRootImpl # performDraw()

调用到ViewRootImpl的draw()。

private void performDraw() {
    // ...
    // mFullRedrawNeeded表示是否需要完全重绘
    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;
    mIsDrawing = true;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        // 调用draw()
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    // ...
}

3. ViewRootImpl # draw()

这个方法非常长,主要是去处理绘制区域、坐标等准备工作,之后调用drawSoftware()。

private void draw(boolean fullRedrawNeeded) {
    // ...
    // 获取需要绘制的区域
    final Rect dirty = mDirty;
    // ...
    // 判断是否需要完全绘制
    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        // 如果需要就将区域设置为屏幕的所有区域
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }
    // ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            // ...
        } else {
            // ...
            // 调用drawSoftware()绘制
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
    // ...
}

4. ViewRootImpl # drawSoftware()

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
    final Canvas canvas;
    try {
        final int left = dirty.left;
        final int top = dirty.top;
        final int right = dirty.right;
        final int bottom = dirty.bottom;
        
        // 创建一个绘制区域的canvas对象
        canvas = mSurface.lockCanvas(dirty);
        
        // 判断lockCanvas有没有改变dirty的顶点值
        if (left != dirty.left || top != dirty.top || right != dirty.right
                || bottom != dirty.bottom) {
            attachInfo.mIgnoreDirtyState = true;
        }
        // 设置画布密度
        canvas.setDensity(mDensity);
    } catch (Surface.OutOfResourcesException e) {
       // ...
    }
    try {
        // ...
        // 先清空画布
        if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
        dirty.setEmpty();
        mIsAnimating = false;
        mView.mPrivateFlags |= View.PFLAG_DRAWN;
        
        try {
            // 设置画布偏移值
            canvas.translate(-xoff, -yoff);
            if (mTranslator != null) {
                mTranslator.translateCanvas(canvas);
            }
            canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
            attachInfo.mSetIgnoreDirtyState = false;
            // 调用draw() 从DecorView开始绘制流程
            mView.draw(canvas);
            // ...
        }
        // ...
    } 
    // ...
    return true;
}

5. DecorView # draw()

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}

6. View # draw()

FrameLayout和ViewGroup都没有重写draw(),所以就调用到了View的draw()。View的draw()也很长,但是注释写的分步很清楚。

/*
 * 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)
 */

绘制背景、绘制自身、绘制子View、绘制阴影、绘制其它的装饰。到这里就调用到Draw向子View分发的过程了。下面就仔细的看看这个方法。

二、View的draw()的实现

根据注释可以将draw()分成六个阶段。

1. 绘制背景

View # draw()

如果不为透明,就调用drawBackground()去绘制背景。

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
        (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

// 判断是否为透明
if (!dirtyOpaque) {
    // 调用drawBackgroud()去绘制背景
    drawBackground(canvas);
}
View # drawBackground()
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }
    // 设置背景图片的边界位置
    setBackgroundBounds();
    
    // 硬件加速相关......
    
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    // 判断View是否滑动
    if ((scrollX | scrollY) == 0) {
        // 没有滑动就绘制背景图片
        background.draw(canvas);
    } else {
        // 如果移动了,就先移动canvas,绘制背景,canvas再移回
        canvas.translate(scrollX, scrollY);
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}
View # setBackgroundBounds()

调用drawable的setBounds()设置边界,传参是由layout过程中生成的顶点值组合的,就相当于是View的顶点。

void setBackgroundBounds() {
    if (mBackgroundSizeChanged && mBackground != null) {
        // 设置背景图的四个坐标,这四个值就是View的四个顶点值
        mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
        mBackgroundSizeChanged = false;
        rebuildOutline();
    }
}

2. 绘制自身

View # draw()
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);
    // ...
}
View # onDraw()

空实现,没有做统一实现,自定义View时需要自己去实现。

protected void onDraw(Canvas canvas) {
}

3. 绘制子View分发过程

View # draw()

在绘制自己完成后就去分发调用子View的绘制过程。

if (!verticalEdges && !horizontalEdges) {
    // ...
    dispatchDraw(canvas);
    // ...
}
ViewGroup # dispatchDraw()

在View中是空实现,所以来看ViewGroup中的实现。会遍历所有的子View,如果子View可见或是存在动画,就调用drawChld()。

@Override
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
    // ...
    // 遍历子View
    for (int i = 0; i < childrenCount; i++) {
        // ...
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            // 调用drawChild去传递
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    // ...
}
ViewGroup # drawChild()

直接调用了子View的draw(),实现了传递。

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

这个draw()有三个参数,和之前分析的一个参数的draw()不同,注释上说得很清楚,这个draw()是ViewGroup.drawChild()调用去绘制子View的。和一个参数最大不同是,这个draw()判断了是绘制缓存内容还是去调用一个参数的draw()绘制。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    // ...
    if (!drawingWithDrawingCache) {
        // 如果不使用缓存
        if (drawingWithRenderNode) {
            // 硬件缓存
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
        } else {
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                // 如果需要跳过,就直接去调用分发给这个View的子View的方法
                dispatchDraw(canvas);
            } else {
                // 调用普通的draw()
                draw(canvas);
            }
        }
    } else if (cache != null) {
        // 如果使用缓存并且缓存不为空
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
            // no layer paint, use temporary paint to draw bitmap
            Paint cachePaint = parent.mCachePaint;
            if (cachePaint == null) {
                cachePaint = new Paint();
                cachePaint.setDither(false);
                parent.mCachePaint = cachePaint;
            }
            cachePaint.setAlpha((int) (alpha * 255));
            // 绘制缓存的内容
            canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
        } else {
            // use layer paint to draw the bitmap, merging the two alphas, but also restore
            int layerPaintAlpha = mLayerPaint.getAlpha();
            if (alpha < 1) {
                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
            }
            // 使用图层的Paint去绘制缓存内容
            canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
            if (alpha < 1) {
                mLayerPaint.setAlpha(layerPaintAlpha);
            }
        }
    }
    // ...
}

4. 绘制装饰物

View # draw()
if (!verticalEdges && !horizontalEdges) {
    // ...
    onDrawForeground(canvas);
    // ...
}
View # onDrawForeground()
public void onDrawForeground(Canvas canvas) {
    // 绘制滚动指示器
    onDrawScrollIndicators(canvas);
    // 绘制滚动条
    onDrawScrollBars(canvas);
    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        // ...
        // 如果有前景图片,就绘制它
        foreground.draw(canvas);
    }
}

5. 如果需要绘制边框阴影

前面那些步骤走完后就能直接return了。如果需要绘制阴影的话,则不会进入前面分析的方法块。而是执行下面的步骤。注释上说这种情况不常见。

View # draw()
// 共同的步骤,绘制背景图片
if (!dirtyOpaque) {
    drawBackground(canvas);
}
// 不需要绘制阴影的情况
if (!verticalEdges && !horizontalEdges) {
    // ...
    return;
}

// 如果需要绘制阴影

// 根据阴影情况去改变参数......

if (solidColor == 0) {
    final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    // 保存图层
    if (drawTop) {
        canvas.saveLayer(left, top, right, top + length, null, flags);
    }
    // ...
} else {
    scrollabilityCache.setFadeColor(solidColor);
}
// 绘制自身
if (!dirtyOpaque) onDraw(canvas);
// 绘制子View
dispatchDraw(canvas);
// ...
// 绘制阴影
if (drawTop) {
    matrix.setScale(1, fadeHeight * topFadeStrength);
    matrix.postTranslate(left, top);
    fade.setLocalMatrix(matrix);
    p.setShader(fade);
    canvas.drawRect(left, top, right, top + length, p);
}
// ...
// 取出保存的图层
canvas.restoreToCount(saveCount);
// ...
// 绘制装饰
onDrawForeground(canvas);
上一篇下一篇

猜你喜欢

热点阅读