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 (малювання переднього плану та смуг прокрутки). Метод не призначений для прямого виклику з коду додатка.

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

Для View (не ViewGroup) метод dispatchDraw порожній — у звичайного View немає дочірніх елементів, тому малювати нічого. Метод існує в базовому класі View, але викликається лише в екземплярах ViewGroup. Розробник може перевірити, чи є у View дочірні елементи, через поведінку dispatchDraw за замовчуванням, але часто простіше перевіряти instanceof ViewGroup.

Конвеєр малювання: onDraw, dispatchDraw, onDrawForeground

Метод draw(Canvas) класу View організовує триетапний конвеєр малювання. Перший етап — виклик onDraw(canvas), у якому View малює свій вміст. Другий етап — виклик dispatchDraw(canvas), що запускає малювання дочірніх View. Третій етап — onDrawForeground(canvas), відповідальний за передній план, смуги прокрутки та ripple-ефекти. Ця послідовність гарантує правильний порядок накладання графічних шарів.

onDraw завжди виконується перед dispatchDraw. Це означає, що вміст батьківського View відображається під дочірніми View. Якщо потрібно намалювати щось поверх дітей, це робиться в onDrawForeground або в перевизначеному dispatchDraw з викликом super.dispatchDraw(canvas) і наступним малюванням поверх. Фон малюється ще до onDraw — у drawBackground(canvas) всередині методу draw().

При апаратному прискоренні конвеєр draw працює через GPU. View малюється в DisplayList — список команд малювання, який кешується та повторно використовується. dispatchDraw в апаратно-прискореному режимі додає DisplayList дочірніх View у загальний DisplayList сцени. Зміна dispatchDraw може призвести до інвалідації DisplayList і перебудови кешу, що впливає на продуктивність.

МетодПорядок викликуПризначення
drawBackground1Малювання фону (background drawable)
onDraw2Малювання вмісту самого View
dispatchDraw3Малювання всіх дочірніх View (лише для ViewGroup)
onDrawForeground4Малювання переднього плану, смуги прокрутки та 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("РЕЖИМ ПОПЕРЕДНЬОГО ПЕРЕГЛЯДУ",
            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) малювання складної анімації в реальному часі (використовуйте 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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