dispatchDraw():是什么,调用机制及与onDraw的区别

作者: IT Sectr 发布日期: 2026-07-22 阅读时间: 9 分钟

dispatchDraw() — 是ViewGroup类中的一个方法,负责在Android层次结构中递归绘制所有子View。根据Android开发者文档(2026),dispatchDraw在主View的draw()方法中的onDraw之后自动调用,遍历所有子元素并调用它们自己的draw()。开发者在自定义ViewGroup中重写dispatchDraw以添加效果、在子元素之上叠加图形或更改绘制顺序。

要点

  • dispatchDraw(Canvas canvas) — ViewGroup的protected方法,在onDraw之后调用以绘制子View
  • draw(Canvas canvas) — View的public final方法,依次调用onDraw、dispatchDraw和onDrawForeground
  • onDraw绘制View自身的内容,而dispatchDraw绘制所有子元素的内容
  • setWillNotDraw(true) — 针对不绘制自身内容、仅绘制子元素的ViewGroup的优化
  • 重写dispatchDraw用于应用效果(模糊、覆盖层)或动画化子元素的绘制

什么是dispatchDraw()?

dispatchDraw(Canvas canvas) — 是ViewGroup类的protected方法,Android系统调用它来绘制当前容器的所有子View。dispatchDraw是标准绘制管道的一部分:首先执行onDraw(绘制View自身的内容),然后执行dispatchDraw(递归绘制子元素),最后执行onDrawForeground(绘制前景和滚动条)。该方法不适用于从应用程序代码直接调用。

ViewGroup中dispatchDraw的标准实现遍历所有子View,检查它们的可见性,并为每个子View调用draw(Canvas)。遍历顺序对应于子元素的索引(从0到childCount - 1)。如果子View重叠,列表中靠后的元素将绘制在靠前元素之上。通过更改dispatchDraw中的顺序,可以改变Z-order绘制顺序。

对于View(非ViewGroup),dispatchDraw方法为空——普通View没有子元素,因此没有可绘制的内容。该方法存在于View基类中,但仅在ViewGroup实例上调用。开发者可以通过dispatchDraw的默认行为检查View是否有子元素,但通常检查instanceof ViewGroup更简单。

绘制管道:onDraw、dispatchDraw、onDrawForeground

View类的draw(Canvas)方法组织了一个三阶段绘制管道。第一阶段——调用onDraw(canvas),View在其中绘制自己的内容。第二阶段——调用dispatchDraw(canvas),启动子View的绘制。第三阶段——onDrawForeground(canvas),负责前景层、滚动条和涟漪效果。该序列保证了图形图层的正确叠加顺序。

onDraw总是在dispatchDraw之前执行。这意味着父View的内容显示在子View之下。如果需要在子元素之上绘制某些内容,可以在onDrawForeground中或在重写的dispatchDraw中调用super.dispatchDraw(canvas)然后在其上进行绘制来完成。背景(background)甚至在onDraw之前绘制——在drawBackground(canvas)方法中,位于draw()方法内部。

硬件加速(hardware acceleration)下,draw管道通过GPU工作。View在DisplayList中绘制——这是一个绘图命令列表,会被缓存和重复使用。dispatchDraw在硬件加速模式下将子View的DisplayList添加到场景的全局DisplayList中。更改dispatchDraw可能导致DisplayList失效和缓存重建,从而影响性能。

方法调用顺序用途
drawBackground1绘制背景(background drawable)
onDraw2绘制View自身的内容
dispatchDraw3绘制所有子View(仅用于ViewGroup)
onDrawForeground4绘制前景、滚动条和涟漪效果

dispatchDraw与onDraw的区别

onDraw负责绘制View自身的内容——形状、文本、图像等属于该特定元素的图形。dispatchDraw负责绘制子View——ViewGroup内部的所有元素。如果ViewGroup没有重写dispatchDraw,则使用ViewGroup中的实现,它简单地递归遍历childCount并为每个子元素调用draw。

对于不绘制自身内容的ViewGroup(例如FrameLayout、LinearLayout),可以通过设置setWillNotDraw(true)来优化onDraw。在这种情况下,onDraw永远不会被调用,从而节省资源。dispatchDraw将继续工作并为子元素调用draw。所有标准ViewGroup(LinearLayout、RelativeLayout、ConstraintLayout)都使用setWillNotDraw(true)。

如果在没有调用super.dispatchDraw(canvas)的情况下重写dispatchDraw,子View将不会被绘制。这对于临时隐藏所有子元素可能有用,但在大多数情况下会导致错误。推荐做法:在重写方法的开头调用super.dispatchDraw(canvas),然后添加您自己的图形(例如带有alpha通道的覆盖层)在子元素之上。

在Kotlin中重写dispatchDraw的示例

让我们创建一个OverlayViewGroup——一个自定义的ViewGroup,它在所有子View之上添加一个带有文本标签的半透明层。dispatchDraw首先调用super(绘制所有子元素),然后绘制覆盖层矩形和文本。Paint在构造函数中创建,以避免在draw循环中进行内存分配。

kotlin
class OverlayViewGroup(context: Context)
    : ViewGroup(context) {

    private val overlayPaint = Paint().apply {
        color = Color.parseColor("#66000000")
        style = Paint.Style.FILL
    }

    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 36f
        isAntiAlias = true
        textAlign = Paint.Align.CENTER
    }

    init {
        setWillNotDraw(false)
    }

    override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), overlayPaint)
        canvas.drawText("预览模式",
            width / 2f,
            height / 2f, textPaint)
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var top = t
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            val ch = child.measuredHeight
            child.layout(l, top, l + cw, top + ch)
            top += ch
        }
    }

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        val maxWidth = resolveSize(
            getChildAt(0).measuredWidth,
            widthMeasureSpec)
        var totalHeight = 0
        for (i in 0 until childCount) {
            totalHeight += getChildAt(i).measuredHeight
        }
        setMeasuredDimension(maxWidth,
            resolveSize(totalHeight, heightMeasureSpec))
    }
}

通过dispatchDraw实现动画Z-order

下面的示例演示了子元素绘制顺序的更改。CircularRevealLayout重写dispatchDraw并以相反顺序绘制子元素,创建了反转的Z-order效果。为了实现动画,基于动画化的offset值添加了绘制索引的循环移位。

kotlin
class ReverseOrderLayout(context: Context)
    : ViewGroup(context) {

    private var reverse = false

    override fun dispatchDraw(canvas: Canvas) {
        if (!reverse) {
            super.dispatchDraw(canvas)
            return
        }
        for (i in childCount - 1 downTo 0) {
            val child = getChildAt(i)
            if (child.visibility == GONE) continue
            drawChild(canvas, child, getDrawingTime())
        }
    }

    fun toggleReverse() {
        reverse = !reverse
        invalidate()
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var left = l
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            child.layout(left, t,
                left + cw, t + child.measuredHeight)
            left += cw
        }
    }

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        var totalWidth = 0
        for (i in 0 until childCount) {
            totalWidth += getChildAt(i).measuredWidth
        }
        setMeasuredDimension(
            resolveSize(totalWidth, widthMeasureSpec),
            resolveSize(getChildAt(0).measuredHeight,
                heightMeasureSpec))
    }
}

何时在Android中使用dispatchDraw

dispatchDraw用于需要影响子View绘制过程而不影响ViewGroup自身内容的场景。主要场景:在所有子元素上应用公共效果(覆盖层、阴影、渐变);更改Z-order顺序以创建深度效果;通过Canvas变换实现子元素的出现或消失动画。

如果需要在子元素之下叠加图形(背景效果),应使用onDraw——dispatchDraw在其之后调用。如果图形应在子元素之上——使用dispatchDraw调用super,然后使用Canvas.draw。如果需要全局颜色或透明度更改,在dispatchDraw中使用canvas.saveLayerAlpha()包裹子元素的绘制会很方便。

不推荐将dispatchDraw用于:1)实时绘制复杂动画(使用invalidate和onDraw);2)创建ViewGroup的屏幕截图(使用buildDrawingCache()View.draw(Canvas));3)修改子View(坐标、尺寸——这是onLayout的任务,不是dispatchDraw的)。dispatchDraw——仅用于应用视觉效果。

常见问题

可以在没有系统的情况下直接调用dispatchDraw吗?

不可以,dispatchDraw是protected方法,由Android系统在公共方法draw()内部调用。直接调用dispatchDraw没有意义,因为它不执行准备工作(保存Canvas、绘制背景和前景)。作为替代,使用View.draw(Canvas)在任意Canvas上进行程序化绘制。

在自定义绘制后调用super.dispatchDraw会发生什么?

如果在您自己的绘制操作之后调用super.dispatchDraw(canvas),子View将被绘制在自定义图形之上。这改变了图层的叠加顺序:先绘制自定义图层,然后绘制子元素。如果需要相反的情况(图形在子元素之上),先调用super.dispatchDraw,然后执行您自己的操作。

dispatchDraw如何影响性能?

dispatchDraw为每个子View调用draw(),总时间与子元素数量成正比。在硬件加速下,在dispatchDraw中添加Canvas操作可能导致DisplayList失效和缓存重建。对于5-10个子元素影响最小,对于50+个子元素,建议缓存复杂的覆盖层。

为什么dispatchDraw不针对View调用?

View(非ViewGroup)没有子元素,因此dispatchDraw不执行有用的工作。然而,该方法存在于View基类中是由于多态性:draw()中的代码为任何View调用dispatchDraw,但View的实现不包含绘制子元素的逻辑。只有ViewGroup重写dispatchDraw。

可以在dispatchDraw中更改Canvas吗?

可以,dispatchDraw接收一个Canvas,可以在调用super.dispatchDraw之前对其进行变换(平移、旋转、缩放)。这用于将整个子元素集合作为一个整体进行动画处理。在完成super.dispatchDraw后,建议将Canvas恢复为原始状态。

总结

  • dispatchDraw(Canvas) — ViewGroup方法,用于在onDraw之后递归绘制所有子View
  • 绘制管道:drawBackground → onDraw → dispatchDraw → onDrawForeground
  • onDraw绘制View自身的内容,dispatchDraw绘制子元素的内容
  • setWillNotDraw(true)优化ViewGroup,跳过onDraw,但dispatchDraw继续工作
  • 重写dispatchDraw用于应用效果、更改Z-order和子元素的Canvas变换
  • super.dispatchDraw和自定义绘制的顺序决定了什么在子元素之上
  • 对于普通View(非ViewGroup),dispatchDraw为空——没有子元素

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读