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) — 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.
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ść.
| Metoda | Kolejność wywołania | Przeznaczenie |
|---|---|---|
| drawBackground | 1 | Rysowanie tła (background drawable) |
| onDraw | 2 | Rysowanie zawartości samego View |
| dispatchDraw | 3 | Rysowanie wszystkich widoków potomnych (tylko dla ViewGroup) |
| onDrawForeground | 4 | Rysowanie foreground, scrollbar i efektu ripple |
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.
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.
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))
}
}
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.
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 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
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.
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.
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.
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.
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
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.
Przeczytaj również