dispatchDraw(): что это, механизм вызова и отличие от onDraw

Автор: IT Sectr Опубликовано: 2026-07-22 Время чтения: 9 мин

dispatchDraw() — это метод класса ViewGroup, отвечающий за рекурсивную отрисовку всех дочерних View в иерархии Android. По данным Android Developers Documentation (2026), dispatchDraw вызывается автоматически после onDraw в методе draw() основного View и проходит по всем дочерним элементам, вызывая их собственный draw(). Разработчики переопределяют dispatchDraw в кастомных ViewGroup для добавления эффектов, наложения графики поверх детей или изменения порядка отрисовки.

Главное

  • dispatchDraw(Canvas canvas) — protected метод ViewGroup, вызываемый после onDraw для отрисовки дочерних View
  • draw(Canvas canvas) — публичный финальный метод View, который последовательно вызывает onDraw, dispatchDraw и onDrawForeground
  • onDraw рисует содержимое самого View, а dispatchDraw — содержимое всех дочерних элементов
  • setWillNotDraw(true) — оптимизация для ViewGroup, которая не рисует собственное содержимое, только дочерние элементы
  • Переопределение dispatchDraw используется для наложения эффектов (blur, overlay) или анимации отрисовки дочерних элементов

Что такое dispatchDraw()?

dispatchDraw(Canvas canvas) — это protected метод класса ViewGroup, который система Android вызывает для отрисовки всех дочерних View текущего контейнера. dispatchDraw является частью стандартного конвейера draw: сначала выполняется onDraw (отрисовка содержимого самого View), затем dispatchDraw (рекурсивная отрисовка детей), затем onDrawForeground (отрисовка foreground и scrollbars). Метод не предназначен для прямого вызова из кода приложения.

Стандартная реализация dispatchDraw в ViewGroup перебирает все дочерние View, проверяет их видимость и вызывает для каждого draw(Canvas). Порядок обхода соответствует индексам детей (от 0 до childCount - 1). Если дочерние View перекрываются, последние в списке рисуются поверх первых. Изменяя порядок в dispatchDraw, можно менять Z-order отрисовки.

Для View (не ViewGroup) метод dispatchDraw пуст — у обычного View нет дочерних элементов, поэтому рисовать нечего. Метод существует в базовом классе View, но вызывается только у экземпляров ViewGroup. Разработчик может проверить, есть ли у View дочерние элементы, через dispatchDraw's дефолтное поведение, но зачастую проще проверять instanceof ViewGroup.

Конвейер отрисовки: onDraw, dispatchDraw, onDrawForeground

Метод draw(Canvas) класса View организует трёхэтапный конвейер отрисовки. Первый этап — вызов onDraw(canvas), в котором View рисует своё содержимое. Второй этап — вызов dispatchDraw(canvas), запускающий рисование дочерних View. Третий этап — onDrawForeground(canvas), отвечающий за foreground-слой, scrollbars и ripple-эффекты. Эта последовательность гарантирует правильный порядок наложения графических слоёв.

onDraw всегда выполняется перед dispatchDraw. Это означает, что содержимое родительского View отображается под дочерними View. Если нужно нарисовать что-то поверх детей, это делается в onDrawForeground или в переопределённом dispatchDraw с вызовом super.dispatchDraw(canvas) и последующим рисованием поверх. Задний фон (background) рисуется ещё до onDraw — в drawBackground(canvas) внутри метода draw().

При аппаратном ускорении (hardware acceleration) конвейер draw работает через GPU. View рисуется в DisplayList — список команд рисования, который кэшируется и повторно используется. dispatchDraw в аппаратно-ускоренном режиме добавляет DisplayList дочерних View в общий DisplayList сцены. Изменение dispatchDraw может привести к инвалидации DisplayList и перестроению кэша, что влияет на производительность.

МетодПорядок вызоваНазначение
drawBackground1Рисование фона (background drawable)
onDraw2Рисование содержимого самого View
dispatchDraw3Рисование всех дочерних View (только для ViewGroup)
onDrawForeground4Рисование foreground, scrollbar и ripple-эффекта

Чем отличается dispatchDraw от onDraw

onDraw отвечает за отрисовку содержимого самого View — фигур, текста, изображений, которые принадлежат этому конкретному элементу. dispatchDraw отвечает за отрисовку дочерних View — всех элементов, которые находятся внутри ViewGroup. Если ViewGroup не переопределяет dispatchDraw, используется реализация из ViewGroup, которая просто рекурсивно проходит по childCount и вызывает draw для каждого ребёнка.

для ViewGroup, которая не рисует собственное содержимое (например, FrameLayout, LinearLayout), onDraw можно оптимизировать установкой setWillNotDraw(true). В этом случае onDraw никогда не вызывается, что экономит ресурсы. dispatchDraw при этом продолжает работать и вызывать draw для дочерних элементов. Все стандартные ViewGroup (LinearLayout, RelativeLayout, ConstraintLayout) используют setWillNotDraw(true).

если переопределить dispatchDraw без вызова super.dispatchDraw(canvas), дочерние View не будут отрисованы. Это может быть полезно для временного скрытия всех детей, но в большинстве случаев вызывает ошибки. Рекомендуемая практика: вызывать super.dispatchDraw(canvas) в начале переопределённого метода, а затем добавлять свою графику (например, overlay с альфа-каналом) поверх детей.

Пример переопределения dispatchDraw на Kotlin

Создадим OverlayViewGroup — кастомную ViewGroup, которая добавляет полупрозрачный слой с текстовой меткой поверх всех дочерних View. dispatchDraw сначала вызывает super (отрисовка всех детей), затем рисует overlay-прямоугольник и текст. 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("PREVIEW MODE",
            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))
    }
}

Анимированный Z-order через dispatchDraw

Следующий пример демонстрирует изменение порядка отрисовки дочерних элементов. 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))
    }
}

Когда использовать dispatchDraw в Android

dispatchDraw используется в сценариях, где нужно повлиять на процесс отрисовки дочерних View, не затрагивая собственное содержимое ViewGroup. Основные сценарии: наложение общего эффекта (overlay, shadow, gradient) поверх всех детей; изменение порядка Z-order для создания эффекта глубины; анимация появления или исчезновения дочерних элементов через Canvas-трансформации.

Если требуется наложение графики под дочерними элементами (background-эффект), следует использовать onDraw — dispatchDraw вызывается после него. Если графика должна быть поверх детей — используйте dispatchDraw с вызовом super, а затем Canvas.draw. Если требуется глобальное изменение цвета или прозрачности, удобно использовать canvas.saveLayerAlpha() в dispatchDraw, оборачивая отрисовку детей.

Не рекомендуется использовать dispatchDraw для: 1) рисования сложной анимации в real-time (используйте invalidate и onDraw); 2) создания скриншотов ViewGroup (используйте buildDrawingCache() или View.draw(Canvas)); 3) модификации дочерних View (координаты, размеры — это задача onLayout, не dispatchDraw). dispatchDraw — только для наложения визуальных эффектов.

Часто задаваемые вопросы

Можно ли вызвать dispatchDraw напрямую без system?

Нет, dispatchDraw — protected метод, вызываемый системой Android внутри публичного метода draw(). Прямой вызов dispatchDraw не имеет смысла, так как он не выполняет подготовительных действий (сохранение Canvas, отрисовка фона и foreground). Вместо этого используйте View.draw(Canvas) для программной отрисовки в произвольный Canvas.

Что произойдёт при вызове super.dispatchDraw после кастомного рисования?

Если вызвать super.dispatchDraw(canvas) после собственных операций рисования, дочерние View будут отрисованы поверх кастомной графики. Это меняет наложение слоёв: сначала рисуется кастомный слой, затем дети. Если нужна обратная ситуация (графика поверх детей), сначала вызывайте super.dispatchDraw, затем свои операции.

Как dispatchDraw влияет на производительность?

dispatchDraw вызывает draw() для каждого дочернего View, суммарное время пропорционально количеству детей. При hardware acceleration добавление Canvas-операций в dispatchDraw может привести к инвалидации DisplayList и перестроению кэша. Для 5–10 детей влияние минимально, для 50+ детей рекомендуется кэшировать сложные наложения.

Почему dispatchDraw не вызывается для View?

View (не ViewGroup) не имеет дочерних элементов, поэтому dispatchDraw не выполняет полезной работы. Однако метод существует в базовом классе View для полиморфизма: код в draw() вызывает dispatchDraw для любого View, но реализация View не содержит логики отрисовки детей. Только ViewGroup переопределяет dispatchDraw.

Можно ли в dispatchDraw изменить Canvas?

Да, dispatchDraw принимает Canvas, и его можно трансформировать перед вызовом super.dispatchDraw (translate, rotate, scale). Это применяется для анимации всего набора детей как единого целого. После завершения super.dispatchDraw рекомендуется восстановить Canvas исходным состоянием.

Итоги

  • dispatchDraw(Canvas) — метод ViewGroup для рекурсивной отрисовки всех дочерних View после onDraw
  • Конвейер draw: drawBackground → onDraw → dispatchDraw → onDrawForeground
  • onDraw рисует содержимое самого View, dispatchDraw — содержимое дочерних элементов
  • setWillNotDraw(true) оптимизирует ViewGroup, пропуская onDraw, но dispatchDraw продолжает работать
  • Переопределение dispatchDraw используется для наложения эффектов, изменения Z-order и Canvas-трансформаций детей
  • Порядок super.dispatchDraw и кастомного рисования определяет, что будет поверх детей
  • Для обычного View (не ViewGroup) dispatchDraw пуст — у него нет дочерних элементов

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также