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) — public final метод на 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, но често е по-лесно да провери 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("РЕЖИМ ЗА ПРЕГЛЕД",
            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 трансформации.

Ако е необходимо наслагване на графика под дъщерни елементи (фонов ефект), трябва да се използва 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 да се извика директно без система?

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

Какво се случва при извикване на super.dispatchDraw след персонализирано рисуване?

Ако извикате super.dispatchDraw(canvas) след собствените си операции по рисуване, дъщерните View ще бъдат нарисувани върху персонализираната графика. Това променя наслагването на слоеве: първо се рисува персонализираният слой, след това децата. Ако е необходима обратната ситуация (графика върху децата), първо извикайте super.dispatchDraw, след това своите операции.

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

dispatchDraw извиква draw() за всяко дъщерно View, общото време е пропорционално на броя на децата. При хардуерно ускорение добавянето на Canvas операции в dispatchDraw може да доведе до инвалидиране на DisplayList и преизграждане на кеша. За 5–10 деца влиянието е минимално, за 50+ деца се препоръчва кеширане на сложни наслагвания.

Защо dispatchDraw не се извиква за View?

View (не ViewGroup) няма дъщерни елементи, затова dispatchDraw не извършва полезна работа. Методът обаче съществува в базовия клас View заради полиморфизма: кодът в draw() извиква dispatchDraw за всяко View, но имплементацията на View не съдържа логика за рисуване на деца. Само ViewGroup презаписва dispatchDraw.

Може ли Canvas да бъде променен в dispatchDraw?

Да, 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също