dispatchDraw() — este o metodă a clasei ViewGroup responsabilă pentru desenarea recursivă a tuturor View-urilor copil în ierarhia Android. Conform Android Developers Documentation (2026), dispatchDraw este apelat automat după onDraw în metoda draw() a View-ului principal și parcurge toate elementele copil, apelând propriul lor draw(). Dezvoltatorii suprascriu dispatchDraw în ViewGroup-uri personalizate pentru a adăuga efecte, a suprapune grafică peste copii sau a schimba ordinea desenării.
Principalul
dispatchDraw(Canvas canvas) — este o metodă protected a clasei ViewGroup pe care sistemul Android o apelează pentru desenarea tuturor View-urilor copil ale containerului curent. dispatchDraw face parte din conducta standard draw: mai întâi se execută onDraw (desenarea conținutului View-ului însuși), apoi dispatchDraw (desenarea recursivă a copiilor), apoi onDrawForeground (desenarea foreground-ului și scrollbar-urilor). Metoda nu este destinată apelării directe din codul aplicației.
Implementarea standard a dispatchDraw în ViewGroup iterează prin toate View-urile copil, verifică vizibilitatea lor și apelează pentru fiecare draw(Canvas). Ordinea de parcurgere corespunde indicilor copiilor (de la 0 la childCount - 1). Dacă View-urile copil se suprapun, ultimele din listă sunt desenate peste primele. Schimbând ordinea în dispatchDraw, se poate modifica Z-order-ul desenării.
Pentru View (nu ViewGroup) metoda dispatchDraw este goală — un View obișnuit nu are elemente copil, deci nu are ce desena. Metoda există în clasa de bază View, dar este apelată doar pentru instanțe ViewGroup. Dezvoltatorul poate verifica dacă View-ul are elemente copil prin comportamentul implicit al dispatchDraw, dar de obicei este mai simplu să verifice instanceof ViewGroup.
Metoda draw(Canvas) a clasei View organizează o conductă de desenare în trei etape. Prima etapă — apelarea onDraw(canvas), în care View-ul își desenează conținutul. A doua etapă — apelarea dispatchDraw(canvas), care declanșează desenarea View-urilor copil. A treia etapă — onDrawForeground(canvas), responsabilă pentru stratul foreground, scrollbar-uri și efectele ripple. Această secvență garantează ordinea corectă de suprapunere a straturilor grafice.
onDraw se execută întotdeauna înainte de dispatchDraw. Aceasta înseamnă că conținutul View-ului părinte este afișat sub View-urile copil. Dacă trebuie desenat ceva deasupra copiilor, aceasta se face în onDrawForeground sau într-un dispatchDraw suprascris cu apelarea super.dispatchDraw(canvas) și desenarea ulterioară deasupra. Fundalul (background) este desenat chiar înainte de onDraw — în drawBackground(canvas) în interiorul metodei draw().
La accelerarea hardware (hardware acceleration) conducta draw funcționează prin GPU. View-ul este desenat într-un DisplayList — o listă de comenzi de desenare care este cache-uită și reutilizată. dispatchDraw în modul accelerat hardware adaugă DisplayList-ul View-urilor copil la DisplayList-ul general al scenei. Modificarea dispatchDraw poate duce la invalidarea DisplayList-ului și reconstruirea cache-ului, ceea ce afectează performanța.
| Metodă | Ordinea apelării | Destinație |
|---|---|---|
| drawBackground | 1 | Desenarea fundalului (background drawable) |
| onDraw | 2 | Desenarea conținutului View-ului însuși |
| dispatchDraw | 3 | Desenarea tuturor View-urilor copil (doar pentru ViewGroup) |
| onDrawForeground | 4 | Desenarea foreground-ului, scrollbar-ului și efectului ripple |
onDraw este responsabil pentru desenarea conținutului View-ului însuși — forme, text, imagini care aparțin acestui element specific. dispatchDraw este responsabil pentru desenarea View-urilor copil — toate elementele care se află în interiorul ViewGroup. Dacă ViewGroup nu suprascrie dispatchDraw, se utilizează implementarea din ViewGroup, care pur și simplu parcurge recursiv childCount și apelează draw pentru fiecare copil.
Pentru ViewGroup care nu își desenează propriul conținut (de exemplu, FrameLayout, LinearLayout), onDraw poate fi optimizat prin setarea setWillNotDraw(true). În acest caz, onDraw nu este niciodată apelat, ceea ce economisește resurse. dispatchDraw continuă să funcționeze și să apeleze draw pentru elementele copil. Toate ViewGroup-urile standard (LinearLayout, RelativeLayout, ConstraintLayout) folosesc setWillNotDraw(true).
Dacă suprascrieți dispatchDraw fără a apela super.dispatchDraw(canvas), View-urile copil nu vor fi desenate. Aceasta poate fi util pentru ascunderea temporară a tuturor copiilor, dar în majoritatea cazurilor provoacă erori. Practică recomandată: apelați super.dispatchDraw(canvas) la începutul metodei suprascrise, apoi adăugați propria grafică (de exemplu, overlay cu canal alfa) deasupra copiilor.
Să creăm OverlayViewGroup — un ViewGroup personalizat care adaugă un strat semi-transparent cu o etichetă text deasupra tuturor View-urilor copil. dispatchDraw mai întâi apelează super (desenarea tuturor copiilor), apoi desenează dreptunghiul overlay și textul. Paint este creat în constructor pentru a evita alocările în bucla 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("MOD PREVIZUALIZARE",
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))
}
}
Următorul exemplu demonstrează schimbarea ordinii de desenare a elementelor copil. CircularRevealLayout suprascrie dispatchDraw și desenează copiii în ordine inversă, creând un efect de Z-order inversat. Pentru animație, se adaugă o deplasare ciclică a indicelui de desenare pe baza valorii animate 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 este utilizat în scenarii în care trebuie să influențați procesul de desenare a View-urilor copil fără a afecta conținutul propriu al ViewGroup. Principalele scenarii: aplicarea unui efect comun (overlay, shadow, gradient) deasupra tuturor copiilor; schimbarea ordinii Z-order pentru a crea un efect de adâncime; animarea apariției sau dispariției elementelor copil prin transformări Canvas.
Dacă este necesară suprapunerea graficii sub elementele copil (efect de fundal), trebuie utilizat onDraw — dispatchDraw este apelat după acesta. Dacă grafica trebuie să fie deasupra copiilor — utilizați dispatchDraw cu apelarea super, apoi Canvas.draw. Dacă este necesară o schimbare globală a culorii sau transparenței, este convenabil să folosiți canvas.saveLayerAlpha() în dispatchDraw, înconjurând desenarea copiilor.
Nu se recomandă utilizarea dispatchDraw pentru: 1) desenarea de animații complexe în timp real (utilizați invalidate și onDraw); 2) crearea de capturi de ecran ale ViewGroup (utilizați buildDrawingCache() sau View.draw(Canvas)); 3) modificarea View-urilor copil (coordonate, dimensiuni — aceasta este sarcina onLayout, nu a dispatchDraw). dispatchDraw — doar pentru aplicarea efectelor vizuale.
Întrebări frecvente
Nu, dispatchDraw — metodă protected apelată de sistemul Android în interiorul metodei publice draw(). Apelarea directă a dispatchDraw nu are sens, deoarece nu execută acțiunile pregătitoare (salvarea Canvas, desenarea fundalului și foreground-ului). În schimb, utilizați View.draw(Canvas) pentru desenarea programatică într-un Canvas arbitrar.
Dacă apelați super.dispatchDraw(canvas) după propriile operații de desenare, View-urile copil vor fi desenate deasupra graficii personalizate. Aceasta modifică suprapunerea straturilor: mai întâi se desenează stratul personalizat, apoi copiii. Dacă este necesară situația inversă (grafică deasupra copiilor), apelați mai întâi super.dispatchDraw, apoi propriile operații.
dispatchDraw apelează draw() pentru fiecare View copil, timpul total fiind proporțional cu numărul de copii. La hardware acceleration, adăugarea operațiilor Canvas în dispatchDraw poate duce la invalidarea DisplayList-ului și reconstruirea cache-ului. Pentru 5–10 copii, impactul este minim, pentru 50+ copii se recomandă cache-uirea suprapunerilor complexe.
View (nu ViewGroup) nu are elemente copil, prin urmare dispatchDraw nu execută nicio muncă utilă. Cu toate acestea, metoda există în clasa de bază View pentru polimorfism: codul din draw() apelează dispatchDraw pentru orice View, dar implementarea View nu conține logica de desenare a copiilor. Doar ViewGroup suprascrie dispatchDraw.
Da, dispatchDraw primește Canvas și poate fi transformat înainte de apelarea super.dispatchDraw (translate, rotate, scale). Aceasta se aplică pentru animarea întregului set de copii ca un întreg unitar. După finalizarea super.dispatchDraw, se recomandă restaurarea Canvas-ului la starea inițială.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și