dispatchDraw(): co to jest, mechanizm wywołania i różnica od onDraw

Autor: IT Sectr Opublikowano: 2026-07-22 Czas czytania: 9 min

dispatchDraw() — to metoda klasy ViewGroup odpowiedzialna za rekurencyjne rysowanie wszystkich widoków potomnych w hierarchii Androida. Według Android Developers Documentation (2026), dispatchDraw jest wywoływany automatycznie po onDraw w metodzie draw() głównego View i przechodzi przez wszystkie elementy potomne, wywołując ich własny draw(). Programiści nadpisują dispatchDraw w niestandardowych ViewGroup, aby dodawać efekty, nakładać grafikę na dzieci lub zmieniać kolejność rysowania.

Najważniejsze

  • dispatchDraw(Canvas canvas) — protected metoda ViewGroup wywoływana po onDraw do rysowania widoków potomnych
  • draw(Canvas canvas) — publiczna finalna metoda View, która kolejno wywołuje onDraw, dispatchDraw i onDrawForeground
  • onDraw rysuje zawartość samego View, a dispatchDraw — zawartość wszystkich elementów potomnych
  • setWillNotDraw(true) — optymalizacja dla ViewGroup, która nie rysuje własnej zawartości, tylko elementy potomne
  • Nadpisywanie dispatchDraw jest używane do nakładania efektów (blur, overlay) lub animacji rysowania elementów potomnych

Co to jest dispatchDraw()?

dispatchDraw(Canvas canvas) — to protected metoda klasy ViewGroup, którą system Android wywołuje do rysowania wszystkich widoków potomnych bieżącego kontenera. dispatchDraw jest częścią standardowego potoku draw: najpierw wykonuje się onDraw (rysowanie zawartości samego View), następnie dispatchDraw (rekurencyjne rysowanie dzieci), a na końcu onDrawForeground (rysowanie foreground i scrollbars). Metoda nie jest przeznaczona do bezpośredniego wywoływania z kodu aplikacji.

Standardowa implementacja dispatchDraw w ViewGroup iteruje przez wszystkie widoki potomne, sprawdza ich widoczność i wywołuje dla każdego draw(Canvas). Kolejność przechodzenia odpowiada indeksom dzieci (od 0 do childCount - 1). Jeśli widoki potomne nachodzą na siebie, ostatnie na liście są rysowane na wierzchu pierwszych. Zmieniając kolejność w dispatchDraw, można zmieniać Z-order rysowania.

Dla View (nie ViewGroup) metoda dispatchDraw jest pusta — zwykły View nie ma elementów potomnych, więc nie ma czego rysować. Metoda istnieje w klasie bazowej View, ale jest wywoływana tylko dla instancji ViewGroup. Programista może sprawdzić, czy View ma elementy potomne, poprzez domyślne zachowanie dispatchDraw, ale często prościej jest sprawdzać instanceof ViewGroup.

Potok rysowania: onDraw, dispatchDraw, onDrawForeground

Metoda draw(Canvas) klasy View organizuje trójetapowy potok rysowania. Pierwszy etap — wywołanie onDraw(canvas), w którym View rysuje swoją zawartość. Drugi etap — wywołanie dispatchDraw(canvas), uruchamiające rysowanie widoków potomnych. Trzeci etap — onDrawForeground(canvas), odpowiedzialny za warstwę foreground, scrollbars i efekty ripple. Ta sekwencja gwarantuje prawidłową kolejność nakładania warstw graficznych.

onDraw zawsze wykonuje się przed dispatchDraw. Oznacza to, że zawartość nadrzędnego View jest wyświetlana pod widokami potomnymi. Jeśli trzeba narysować coś na wierzchu dzieci, robi się to w onDrawForeground lub w nadpisanym dispatchDraw z wywołaniem super.dispatchDraw(canvas) i późniejszym rysowaniem na wierzchu. Tło (background) jest rysowane jeszcze przed onDraw — w drawBackground(canvas) wewnątrz metody draw().

Przy przyspieszeniu sprzętowym (hardware acceleration) potok draw działa przez GPU. View jest rysowany w DisplayList — liście poleceń rysowania, która jest buforowana i ponownie używana. dispatchDraw w trybie przyspieszenia sprzętowego dodaje DisplayList widoków potomnych do ogólnej DisplayList sceny. Zmiana dispatchDraw może prowadzić do unieważnienia DisplayList i przebudowy bufora, co wpływa na wydajność.

MetodaKolejność wywołaniaPrzeznaczenie
drawBackground1Rysowanie tła (background drawable)
onDraw2Rysowanie zawartości samego View
dispatchDraw3Rysowanie wszystkich widoków potomnych (tylko dla ViewGroup)
onDrawForeground4Rysowanie foreground, scrollbar i efektu ripple

Czym różni się dispatchDraw od onDraw

onDraw odpowiada za rysowanie zawartości samego View — kształtów, tekstu, obrazów, które należą do tego konkretnego elementu. dispatchDraw odpowiada za rysowanie widoków potomnych — wszystkich elementów, które znajdują się wewnątrz ViewGroup. Jeśli ViewGroup nie nadpisuje dispatchDraw, używana jest implementacja z ViewGroup, która po prostu rekurencyjnie przechodzi przez childCount i wywołuje draw dla każdego dziecka.

Dla ViewGroup, która nie rysuje własnej zawartości (np. FrameLayout, LinearLayout), onDraw można zoptymalizować ustawieniem setWillNotDraw(true). W tym przypadku onDraw nigdy nie jest wywoływany, co oszczędza zasoby. dispatchDraw natomiast nadal działa i wywołuje draw dla elementów potomnych. Wszystkie standardowe ViewGroup (LinearLayout, RelativeLayout, ConstraintLayout) używają setWillNotDraw(true).

Jeśli nadpisać dispatchDraw bez wywołania super.dispatchDraw(canvas), widoki potomne nie zostaną narysowane. Może to być przydatne do tymczasowego ukrycia wszystkich dzieci, ale w większości przypadków powoduje błędy. Zalecana praktyka: wywoływać super.dispatchDraw(canvas) na początku nadpisanej metody, a następnie dodawać własną grafikę (np. overlay z kanałem alfa) na wierzchu dzieci.

Przykład nadpisania dispatchDraw w Kotlin

Stwórzmy OverlayViewGroup — niestandardową ViewGroup, która dodaje półprzezroczystą warstwę z etykietą tekstową na wierzchu wszystkich widoków potomnych. dispatchDraw najpierw wywołuje super (rysowanie wszystkich dzieci), następnie rysuje prostokąt overlay i tekst. Paint jest tworzony w konstruktorze, aby uniknąć alokacji w pętli 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("TRYB PODGLĄDU",
            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))
    }
}

Animowany Z-order przez dispatchDraw

Następny przykład demonstruje zmianę kolejności rysowania elementów potomnych. CircularRevealLayout nadpisuje dispatchDraw i rysuje dzieci w odwrotnej kolejności, tworząc efekt odwróconego Z-order. Do animacji dodawane jest cykliczne przesunięcie indeksu rysowania na podstawie animowanej wartości 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))
    }
}

Kiedy używać dispatchDraw w Android

dispatchDraw jest używany w scenariuszach, gdzie trzeba wpłynąć na proces rysowania widoków potomnych, nie naruszając własnej zawartości ViewGroup. Główne scenariusze: nakładanie wspólnego efektu (overlay, shadow, gradient) na wierzchu wszystkich dzieci; zmiana kolejności Z-order dla tworzenia efektu głębi; animacja pojawiania się lub znikanie elementów potomnych przez transformacje Canvas.

Jeśli wymagane jest nałożenie grafiki pod elementami potomnymi (efekt tła), należy użyć onDraw — dispatchDraw jest wywoływany po nim. Jeśli grafika ma być na wierzchu dzieci — użyj dispatchDraw z wywołaniem super, a następnie Canvas.draw. Jeśli wymagana jest globalna zmiana koloru lub przezroczystości, wygodnie jest użyć canvas.saveLayerAlpha() w dispatchDraw, zawijając rysowanie dzieci.

Nie zaleca się używania dispatchDraw do: 1) rysowania złożonej animacji w czasie rzeczywistym (użyj invalidate i onDraw); 2) tworzenia zrzutów ekranu ViewGroup (użyj buildDrawingCache() lub View.draw(Canvas)); 3) modyfikacji widoków potomnych (współrzędne, rozmiary — to zadanie onLayout, nie dispatchDraw). dispatchDraw — tylko do nakładania efektów wizualnych.

Często zadawane pytania

Czy można wywołać dispatchDraw bezpośrednio bez systemu?

Nie, dispatchDraw — protected metoda wywoływana przez system Android wewnątrz publicznej metody draw(). Bezpośrednie wywołanie dispatchDraw nie ma sensu, ponieważ nie wykonuje on czynności przygotowawczych (zapisanie Canvas, rysowanie tła i foreground). Zamiast tego użyj View.draw(Canvas) do programowego rysowania w dowolnym Canvas.

Co się stanie przy wywołaniu super.dispatchDraw po niestandardowym rysowaniu?

Jeśli wywołasz super.dispatchDraw(canvas) po własnych operacjach rysowania, widoki potomne zostaną narysowane na wierzchu niestandardowej grafiki. To zmienia nakładanie warstw: najpierw rysowana jest warstwa niestandardowa, następnie dzieci. Jeśli potrzebna jest odwrotna sytuacja (grafika na wierzchu dzieci), najpierw wywołaj super.dispatchDraw, a następnie własne operacje.

Jak dispatchDraw wpływa na wydajność?

dispatchDraw wywołuje draw() dla każdego widoku potomnego, całkowity czas jest proporcjonalny do liczby dzieci. Przy hardware acceleration dodawanie operacji Canvas w dispatchDraw może prowadzić do unieważnienia DisplayList i przebudowy bufora. Dla 5–10 dzieci wpływ jest minimalny, dla 50+ dzieci zaleca się buforowanie złożonych nakładek.

Dlaczego dispatchDraw nie jest wywoływany dla View?

View (nie ViewGroup) nie ma elementów potomnych, dlatego dispatchDraw nie wykonuje użytecznej pracy. Jednak metoda istnieje w klasie bazowej View dla polimorfizmu: kod w draw() wywołuje dispatchDraw dla każdego View, ale implementacja View nie zawiera logiki rysowania dzieci. Tylko ViewGroup nadpisuje dispatchDraw.

Czy można w dispatchDraw zmienić Canvas?

Tak, dispatchDraw przyjmuje Canvas i można go transformować przed wywołaniem super.dispatchDraw (translate, rotate, scale). Jest to stosowane do animacji całego zestawu dzieci jako jednej całości. Po zakończeniu super.dispatchDraw zaleca się przywrócenie Canvas do stanu początkowego.

Podsumowanie

  • dispatchDraw(Canvas) — metoda ViewGroup do rekurencyjnego rysowania wszystkich widoków potomnych po onDraw
  • Potok draw: drawBackground → onDraw → dispatchDraw → onDrawForeground
  • onDraw rysuje zawartość samego View, dispatchDraw — zawartość elementów potomnych
  • setWillNotDraw(true) optymalizuje ViewGroup, pomijając onDraw, ale dispatchDraw nadal działa
  • Nadpisywanie dispatchDraw jest używane do nakładania efektów, zmiany Z-order i transformacji Canvas dzieci
  • Kolejność super.dispatchDraw i niestandardowego rysowania określa, co będzie na wierzchu dzieci
  • Dla zwykłego View (nie ViewGroup) dispatchDraw jest pusty — nie ma elementów potomnych

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również