图+源码,读懂View的Draw方法(一)

简介: 本文将继续讲述 View 绘制三大方法的最后一个方法——Draw 方法。该方法不会很复杂,相信大家很快可以弄懂。

前置知识

  • 有Android开发基础
  • 了解 View 体系
  • 了解 View 的 MeasureLayout 方法

前言

在上一篇文章中,笔者带大家学习了 ViewLayout 流程。这个流程很简单,当程序进到 layout() 方法,执行 setFrame() 和重写的 onLayout() 方法,使用 onLayout() 方法继续遍历其余子元素,就可以找出 View 树所有元素的位置。

本文将继续讲述 View 绘制三大方法的最后一个方法——Draw 方法。该方法不会很复杂,相信大家很快可以弄懂。

Draw 方法的作用和入口

Draw 翻译为绘画。其方法的作用是绘制界面,是 View 绘制流程的最后一步。

我们依旧和上文一样,先来看一下该方法的入口是什么,从入口到 draw 方法又是怎么样的一个流程?

第一步依旧是在 ViewRootImpl 的类中找到 performTraversals() 方法,该方法调用的第三个重要的绘制方法就是 performDraw() 。关于这一点,大家感兴趣的话,可以到 图+源码,读懂View的Measure方法 - 掘金 (juejin.cn) 一文查看。 performDraw()draw 方法的入口处,文章这一部分将讲述从 performDraw() 到 View 中 draw() 方法的流程。

首先,我们进入 performDraw() 方法,这里可以看到在下面代码的注释1处,调用了一个该类下的 draw() 方法。但是很显然,这个 boolean 类型的 draw() 方法并非我们所需要的 View 下面的 draw() 方法。

private boolean performDraw() {
    ...
    try {
        boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);//1
        ...
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
  ...
    return true;
}
复制代码

继续在 ViewRootImpl 类下的查看这个 boolean 类型的 draw() 方法。我们忽略一些无关的代码,可以在下方的注释1处看到其调用了一个 drawSoftware() 方法。事实上,这个方法是和 View 中的 draw() 方法有关系的,我们继续往下边查看。

private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
    ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (isHardwareEnabled()) {
            ...
        } else {
            ...
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                              scalingRequired, dirty, surfaceInsets)) {//1
                return false;
            }
        }
    }
    ...
    return useAsyncReport;
}
复制代码

再次点击进去 drawSoftware() 方法查看,我们终于在下面注释2处,看到了 mView.draw(canvas) 字段。可见,View 中的 draw() 方法是在此处被调用的。由此,我们终于看到了 draw 流程的入口。

/**
 * @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) {
    ...
    try {
        ...
        mView.draw(canvas);//2
        drawAccessibilityFocusedDrawableIfNeeded(canvas);
    } finally {
        ...
    }
    return true;
}
复制代码

上面的流程,我们可以用一张图来展示,希望对你的理解有帮助。

1.webp.jpg

Draw流程

源码分析

点进 View 的 draw() 方法的源码,我们首先看方法的注释。方法的注释大意是说:

  1. 在调用此绘制方法之前,需要已经完成了所有视图的 layout 流程;
  2. 而在实现一个自定义 View 的时候,要重写实现 onDraw() 方法,而不是 draw() 这个方法;
  3. 如果确实需要重写此方法( draw() ),请调用超类版本( surper )。

再次往下面查看,我们会发现方法内部还有一段注释,该注释写明了 Draw 流程的每个步骤。我们翻译为中文是下面这样子。

  1. 绘制背景
  2. 如果有必要,保存 canvas 层以准备逐渐淡出绘制的内容(可以不执行)
  3. 绘制 View 的内容
  4. 绘制子 View 的内容
  5. 如果有必要,绘制 View 的渐变淡出边缘(类似阴影效果)和修复层级(可以不执行)
  6. 绘制装饰,例如滚动条
  7. 如果有必要,绘制默认的焦点高亮显示(可以不执行)

上面的7个步骤中,他们的执行顺序是固定的,且其中的 2 3 7 不是必要执行的步骤。下面的代码省略了一段包含全部步骤的详细执行代码,感兴趣的同学可以点击查看。下面我们就逐个查看必要执行的步骤执行了什么。

/**
 * 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)
     *      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;
    }
    ...//源码此处编写了一个完整的程序例程,包括了前面省略的第二步和第5步。由于篇幅原因,此处省略,大家可以点击参考处的详细链接查看
}
复制代码



相关文章
|
1月前
|
开发工具 数据安全/隐私保护 git
NewspaceGPT绘制流程图
NewspaceGPT绘制流程图
19 2
|
3月前
|
XML 前端开发 数据可视化
View的绘制流程
View的绘制流程
21 1
图+源码,读懂View的Draw方法(二)
本文将继续讲述 View 绘制三大方法的最后一个方法——Draw 方法。该方法不会很复杂,相信大家很快可以弄懂。
图+源码,读懂View的Draw方法(二)
|
Android开发
图+源码,读懂View的Measure方法
本篇是 读懂View 系列的第二篇文章,本文将给大家正式开始讲解View绘制的三大方法,本篇将讲述第一个方法—— Measure 方法。
图+源码,读懂View的Measure方法
|
存储 Android开发
图+源码,读懂View的MeasureSpec
今天这篇文章,我们讲解的是 Measure 方法的前置知识,View的MeasureSpec类。
图+源码,读懂View的MeasureSpec
|
Android开发
图+源码,读懂View的Layout方法
本篇文章就带大家学习 View 绘制三大方法的第二个方法——Layout 方法。
图+源码,读懂View的Layout方法
|
程序员
Flutter:如何使用 CustomPaint 绘制心形
作为程序员其实也有浪漫的一幕,今天我们一起借助CustomPaint和CustomPainter绘制心形,本文将带您了解在 Flutter 中使用CustomPaint和CustomPainter绘制心形的端到端示例。闲话少说(比如谈论 Flutter 的历史或它有多华丽),让我们深入研究代码并制作一些东西。
185 0
Flutter:如何使用 CustomPaint 绘制心形
|
API vr&ar 图形学
【100个 Unity小知识点】☀️ | Unity中使用代码查询Draw call、Tris和Verts等信息
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。 也可以简单把 Unity 理解为一个游戏引擎,可以用来专业制作游戏!
【100个 Unity小知识点】☀️ | Unity中使用代码查询Draw call、Tris和Verts等信息
|
前端开发 vr&ar 容器
Flutter 115: 图解自定义 View 之 Canvas (四) drawParagraph
0 基础学习 Flutter,第一百一十五节:自定义 Canvas 第四节,文本绘制小结!
635 0
Flutter 115: 图解自定义 View 之 Canvas (四) drawParagraph