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

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

首先查看 step1 的 drawBackground() 方法,在下面的注释1处,我们可以其对偏移量进行了判断,当偏移量为0的时候,就直接绘制;当偏移量不为0的时候,我们先进行偏移再执行背景的绘制。注意此处执行的 draw 方法是在 Drawable 里面的。

/**
 * Draws the background onto the specified canvas.
 *
 * @param canvas Canvas on which to draw the background
 */
@UnsupportedAppUsage
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }
    setBackgroundBounds();
    ...
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {//1
        background.draw(canvas);
    } else {//2
        canvas.translate(scrollX, scrollY);
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}
复制代码

然后我们看 step 3 的 onDraw() 方法,我们发现这是个空实现的方法,在注释处标注了需要自定义 View 的时候实现它

/**
 * Implement this to do your drawing.
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) {
}
复制代码

我们继续看 step 4 的 dispatchDraw() 方法,这也是一个空方法。注释处标明:需要在派生类(继承类)绘制了自身之后,绘制子类之前,对该方法进行重写调用。在 ViewGroup 中,有对这个方法进行重写,我们可以继续到 ViewGroup  查看。

/**
 * 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) {
}
复制代码

ViewGroup 中,我们在下列注释1处看到 dispatchDraw() 方法对子元素执行了遍历,在注释2 3 处,可以看到其对每一个子 View 都执行了 drawChild() 方法。

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    for (int i = 0; i < childrenCount; i++) {//1
        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);
        }
    }
    ...
}
复制代码

而点开 drawChild() 方法,我们发现它是直接调用了子 View 中 boolean 类型的 draw() 方法(并非我们上文提到的执行最终绘制的 draw() 方法)

/**
 * 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 中 boolean 类型的 draw() 方法,在注释1处检测是否无缓存,如果无缓存就直接执行绘制;如果有缓存,就在注释2处利用缓存显示。

/**
 * 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) {
  ...
    if (!drawingWithDrawingCache) {//1
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((RecordingCanvas) canvas).drawRenderNode(renderNode);
        } else {
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }
        }
    } else if (cache != null) {//2,有缓存
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
            // no layer paint, use temporary paint to draw bitmap
            ...
        } else {
            // use layer paint to draw the bitmap, merging the two alphas, but also restore
            ...
        }
    }
  ...
    return more;
}
复制代码

最后,我们来看 step 6 的 onDrawForeground() 方法,我们可以在下方的注释1处可以看到,它绘制的是 ScrollBar 类型的装饰。并在注释2处调用 foreground.draw() 方法绘制视图内容的上层视图。

/**
 * Draw any foreground content for this view.
 *
 * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
 * drawable or other view-specific decorations. The foreground is drawn on top of the
 * primary view content.</p>
 *
 * @param canvas canvas to draw into
 */
public void onDrawForeground(Canvas canvas) {
    onDrawScrollIndicators(canvas);
    onDrawScrollBars(canvas);//1
    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        ...
        foreground.draw(canvas);//2
    }
}
复制代码

流程图展示

上文的源码分析到此结束了,笔者按照上面的流程整理出来了一张 Draw 方法的流程图,希望可以帮助大家更好的理解。

1.webp.jpg

相关文章
|
2月前
|
XML 前端开发 数据可视化
View的绘制流程
View的绘制流程
15 1
|
2月前
|
前端开发 Android开发 容器
自定义View之View的工作原理
自定义View之View的工作原理
25 0
|
前端开发 Android开发
图+源码,读懂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方法
|
前端开发
如何使用canvas进行画图
如何使用canvas进行画图
103 0
|
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 第四节,文本绘制小结!
616 0
Flutter 115: 图解自定义 View 之 Canvas (四) drawParagraph