dispatchDraw(): co to je, mechanismus volání a rozdíl od onDraw

Autor: IT Sectr Publikováno: 2026-07-22 Doba čtení: 9 min

dispatchDraw() — je metoda třídy ViewGroup odpovědná za rekurzivní kreslení všech podřízených View v hierarchii Android. Podle Android Developers Documentation (2026) je dispatchDraw automaticky volán po onDraw v metodě draw() hlavního View a prochází všechny podřízené prvky a volá jejich vlastní draw(). Vývojáři přepisují dispatchDraw ve vlastních ViewGroup pro přidávání efektů, vkládání grafiky přes děti nebo změnu pořadí kreslení.

Hlavní

  • dispatchDraw(Canvas canvas) — protected metoda ViewGroup volaná po onDraw pro kreslení podřízených View
  • draw(Canvas canvas) — public final metoda View, která postupně volá onDraw, dispatchDraw a onDrawForeground
  • onDraw kreslí obsah samotného View, dispatchDraw — obsah všech podřízených prvků
  • setWillNotDraw(true) — optimalizace pro ViewGroup, která nekreslí vlastní obsah, pouze podřízené prvky
  • Přepsání dispatchDraw se používá pro aplikaci efektů (blur, overlay) nebo animaci kreslení podřízených prvků

Co je dispatchDraw()?

dispatchDraw(Canvas canvas) — je protected metoda třídy ViewGroup, kterou systém Android volá pro kreslení všech podřízených View aktuálního kontejneru. dispatchDraw je součástí standardního potrubí draw: nejprve se provede onDraw (kreslení obsahu samotného View), poté dispatchDraw (rekurzivní kreslení dětí), poté onDrawForeground (kreslení foreground a scrollbarů). Metoda není určena k přímému volání z kódu aplikace.

Standardní implementace dispatchDraw ve ViewGroup iteruje všemi podřízenými View, kontroluje jejich viditelnost a volá pro každé draw(Canvas). Pořadí průchodu odpovídá indexům dětí (od 0 do childCount - 1). Pokud se podřízené View překrývají, poslední v seznamu se kreslí přes první. Změnou pořadí v dispatchDraw lze změnit Z-order kreslení.

Pro View (ne ViewGroup) je metoda dispatchDraw prázdná — běžné View nemá podřízené prvky, takže není co kreslit. Metoda existuje v základní třídě View, ale je volána pouze u instancí ViewGroup. Vývojář může zkontrolovat, zda má View podřízené prvky, pomocí výchozího chování dispatchDraw, ale často je jednodušší zkontrolovat instanceof ViewGroup.

Potrubí kreslení: onDraw, dispatchDraw, onDrawForeground

Metoda draw(Canvas) třídy View organizuje třífázové potrubí kreslení. První fáze — volání onDraw(canvas), ve kterém View kreslí svůj obsah. Druhá fáze — volání dispatchDraw(canvas), které spouští kreslení podřízených View. Třetí fáze — onDrawForeground(canvas), odpovědný za vrstvu foreground, scrollbary a efekty ripple. Tato sekvence zaručuje správné pořadí překrývání grafických vrstev.

onDraw se vždy provádí před dispatchDraw. To znamená, že obsah nadřazeného View se zobrazuje pod podřízenými View. Pokud je třeba nakreslit něco přes děti, děje se tak v onDrawForeground nebo v přepsaném dispatchDraw s voláním super.dispatchDraw(canvas) a následným kreslením přes. Pozadí (background) se kreslí ještě před onDraw — v drawBackground(canvas) uvnitř metody draw().

Při hardwarové akceleraci (hardware acceleration) pracuje potrubí draw přes GPU. View se kreslí do DisplayList — seznamu příkazů kreslení, který je ukládán do mezipaměti a znovu používán. dispatchDraw v režimu hardwarové akcelerace přidává DisplayList podřízených View do obecného DisplayList scény. Změna dispatchDraw může vést k zneplatnění DisplayList a přestavbě mezipaměti, což ovlivňuje výkon.

MetodaPořadí voláníÚčel
drawBackground1Kreslení pozadí (background drawable)
onDraw2Kreslení obsahu samotného View
dispatchDraw3Kreslení všech podřízených View (pouze pro ViewGroup)
onDrawForeground4Kreslení foreground, scrollbar a ripple efektu

Čím se liší dispatchDraw od onDraw

onDraw je zodpovědný za kreslení obsahu samotného View — tvarů, textu, obrázků, které patří k tomuto konkrétnímu prvku. dispatchDraw je zodpovědný za kreslení podřízených View — všech prvků, které se nacházejí uvnitř ViewGroup. Pokud ViewGroup nepřepisuje dispatchDraw, použije se implementace z ViewGroup, která jednoduše rekurzivně prochází childCount a volá draw pro každé dítě.

Pro ViewGroup, která nekreslí vlastní obsah (např. FrameLayout, LinearLayout), lze onDraw optimalizovat nastavením setWillNotDraw(true). V tomto případě není onDraw nikdy volán, což šetří prostředky. dispatchDraw přitom nadále funguje a volá draw pro podřízené prvky. Všechny standardní ViewGroup (LinearLayout, RelativeLayout, ConstraintLayout) používají setWillNotDraw(true).

Pokud je dispatchDraw přepsán bez volání super.dispatchDraw(canvas), podřízené View nebudou nakresleny. To může být užitečné pro dočasné skrytí všech dětí, ale ve většině případů způsobuje chyby. Doporučená praxe: volejte super.dispatchDraw(canvas) na začátku přepsané metody a poté přidejte vlastní grafiku (např. overlay s alfa kanálem) přes děti.

Příklad přepsání dispatchDraw v Kotlin

Vytvořme OverlayViewGroup — vlastní ViewGroup, která přidává poloprůhlednou vrstvu s textovým štítkem přes všechny podřízené View. dispatchDraw nejprve volá super (kreslení všech dětí), poté kreslí obdélník overlay a text. Paint je vytvořen v konstruktoru, aby se zabránilo alokacím ve smyčce 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("REŽIM NÁHLEDU",
            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))
    }
}

Animovaný Z-order přes dispatchDraw

Následující příklad ukazuje změnu pořadí kreslení podřízených prvků. CircularRevealLayout přepisuje dispatchDraw a kreslí děti v obráceném pořadí, čímž vytváří efekt obráceného Z-order. Pro animaci je přidán cyklický posun indexu kreslení na základě animované hodnoty 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))
    }
}

Kdy použít dispatchDraw v Android

dispatchDraw se používá ve scénářích, kde je potřeba ovlivnit proces kreslení podřízených View, aniž by byl ovlivněn vlastní obsah ViewGroup. Hlavní scénáře: aplikace společného efektu (overlay, shadow, gradient) přes všechny děti; změna pořadí Z-order pro vytvoření efektu hloubky; animace objevování nebo mizení podřízených prvků pomocí Canvas transformací.

Pokud je vyžadováno vložení grafiky pod podřízené prvky (efekt pozadí), použijte onDraw — dispatchDraw je volán po něm. Pokud má být grafika přes děti — použijte dispatchDraw s voláním super, poté Canvas.draw. Pokud je vyžadována globální změna barvy nebo průhlednosti, je vhodné použít canvas.saveLayerAlpha() v dispatchDraw, obalující kreslení dětí.

Nedoporučuje se používat dispatchDraw pro: 1) kreslení složité animace v reálném čase (použijte invalidate a onDraw); 2) vytváření snímků obrazovky ViewGroup (použijte buildDrawingCache() nebo View.draw(Canvas)); 3) úpravu podřízených View (souřadnice, velikosti — to je úkol onLayout, ne dispatchDraw). dispatchDraw — pouze pro aplikaci vizuálních efektů.

Často kladené otázky

Lze dispatchDraw volat přímo bez systému?

Ne, dispatchDraw — protected metoda volaná systémem Android uvnitř veřejné metody draw(). Přímé volání dispatchDraw nemá smysl, protože neprovádí přípravné akce (uložení Canvas, kreslení pozadí a foreground). Místo toho použijte View.draw(Canvas) pro programové kreslení do libovolného Canvas.

Co se stane při volání super.dispatchDraw po vlastním kreslení?

Pokud zavoláte super.dispatchDraw(canvas) po vlastních operacích kreslení, podřízené View budou nakresleny přes vlastní grafiku. Tím se změní překrývání vrstev: nejprve se kreslí vlastní vrstva, poté děti. Pokud je potřeba opačná situace (grafika přes děti), nejprve zavolejte super.dispatchDraw a poté vlastní operace.

Jak dispatchDraw ovlivňuje výkon?

dispatchDraw volá draw() pro každé podřízené View, celkový čas je úměrný počtu dětí. Při hardwarové akceleraci může přidání Canvas operací v dispatchDraw vést k zneplatnění DisplayList a přestavbě mezipaměti. Pro 5–10 dětí je dopad minimální, pro 50+ dětí se doporučuje ukládání složitých překryvů do mezipaměti.

Proč dispatchDraw není volán pro View?

View (ne ViewGroup) nemá podřízené prvky, proto dispatchDraw nevykonává užitečnou práci. Metoda však existuje v základní třídě View kvůli polymorfismu: kód v draw() volá dispatchDraw pro jakékoli View, ale implementace View neobsahuje logiku kreslení dětí. Pouze ViewGroup přepisuje dispatchDraw.

Lze v dispatchDraw změnit Canvas?

Ano, dispatchDraw přijímá Canvas a lze jej transformovat před voláním super.dispatchDraw (translate, rotate, scale). To se používá pro animaci celé sady dětí jako jednoho celku. Po dokončení super.dispatchDraw se doporučuje obnovit Canvas do původního stavu.

Shrnutí

  • dispatchDraw(Canvas) — metoda ViewGroup pro rekurzivní kreslení všech podřízených View po onDraw
  • Potrubí draw: drawBackground → onDraw → dispatchDraw → onDrawForeground
  • onDraw kreslí obsah samotného View, dispatchDraw — obsah podřízených prvků
  • setWillNotDraw(true) optimalizuje ViewGroup, přeskočí onDraw, ale dispatchDraw dále funguje
  • Přepsání dispatchDraw se používá pro aplikaci efektů, změnu Z-order a Canvas transformace dětí
  • Pořadí super.dispatchDraw a vlastního kreslení určuje, co bude přes dětmi
  • Pro běžné View (ne ViewGroup) je dispatchDraw prázdný — nemá podřízené prvky

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také