dispatchDraw() — är en metod i klassen ViewGroup som ansvarar för rekursiv ritning av alla barn-View i Android-hierarkin. Enligt Android Developers Documentation (2026) anropas dispatchDraw automatiskt efter onDraw i draw()-metoden för huvud-View och går igenom alla barnelement och anropar deras egen draw(). Utvecklare åsidosätter dispatchDraw i anpassade ViewGroup för att lägga till effekter, lägga grafik över barn eller ändra ritningsordningen.
Huvudsakligt
dispatchDraw(Canvas canvas) — är en protected metod i klassen ViewGroup som Android-systemet anropar för att rita alla barn-View i den aktuella containern. dispatchDraw är en del av standardritningspipelinen: först körs onDraw (ritning av själva View:s innehåll), sedan dispatchDraw (rekursiv ritning av barn), sedan onDrawForeground (ritning av foreground och scrollbars). Metoden är inte avsedd att anropas direkt från applikationskoden.
Standardimplementeringen av dispatchDraw i ViewGroup itererar genom alla barn-View, kontrollerar deras synlighet och anropar för varje draw(Canvas). Genomgångsordningen motsvarar barnens index (från 0 till childCount - 1). Om barn-View överlappar varandra, ritas de sista i listan över de första. Genom att ändra ordningen i dispatchDraw kan Z-order för ritning ändras.
För View (inte ViewGroup) är dispatchDraw-metoden tom — en vanlig View har inga barnelement, så det finns inget att rita. Metoden finns i basklassen View, men anropas endast för ViewGroup-instanser. Utvecklaren kan kontrollera om View har barnelement via dispatchDraw:s standardbeteende, men det är ofta enklare att kontrollera instanceof ViewGroup.
Metoden draw(Canvas) i klassen View organiserar en trestegs ritningspipeline. Första steget — anrop av onDraw(canvas), där View ritar sitt innehåll. Andra steget — anrop av dispatchDraw(canvas), som startar ritning av barn-View. Tredje steget — onDrawForeground(canvas), ansvarig för foreground-lager, scrollbars och ripple-effekter. Denna sekvens garanterar korrekt ordning för överlappning av grafiska lager.
onDraw körs alltid före dispatchDraw. Detta innebär att innehållet i den överordnade View visas under barn-View. Om något behöver ritas ovanpå barnen görs detta i onDrawForeground eller i en åsidosatt dispatchDraw med anrop av super.dispatchDraw(canvas) och efterföljande ritning ovanpå. Bakgrunden (background) ritas redan före onDraw — i drawBackground(canvas) inuti metoden draw().
Vid hårdvaruacceleration (hardware acceleration) fungerar ritningspipelinen via GPU. View ritas i en DisplayList — en lista med ritkommandon som cachas och återanvänds. dispatchDraw i hårdvaruaccelererat läge lägger till barn-View:s DisplayList till scenens allmänna DisplayList. Ändring av dispatchDraw kan leda till ogiltigförklaring av DisplayList och återuppbyggnad av cachen, vilket påverkar prestanda.
| Metod | Anropsordning | Syfte |
|---|---|---|
| drawBackground | 1 | Ritning av bakgrunden (background drawable) |
| onDraw | 2 | Ritning av själva View:s innehåll |
| dispatchDraw | 3 | Ritning av alla barn-View (endast för ViewGroup) |
| onDrawForeground | 4 | Ritning av foreground, scrollbar och ripple-effekt |
onDraw ansvarar för att rita innehållet i själva View — former, text, bilder som tillhör detta specifika element. dispatchDraw ansvarar för att rita barn-View — alla element som finns inuti ViewGroup. Om ViewGroup inte åsidosätter dispatchDraw används implementeringen från ViewGroup, som helt enkelt rekursivt går igenom childCount och anropar draw för varje barn.
För ViewGroup som inte ritar eget innehåll (t.ex. FrameLayout, LinearLayout) kan onDraw optimeras genom att ställa in setWillNotDraw(true). I detta fall anropas onDraw aldrig, vilket sparar resurser. dispatchDraw fortsätter att fungera och anropa draw för barnelement. Alla standard ViewGroup (LinearLayout, RelativeLayout, ConstraintLayout) använder setWillNotDraw(true).
Om dispatchDraw åsidosätts utan att anropa super.dispatchDraw(canvas) kommer barn-View inte att ritas. Detta kan vara användbart för tillfällig döljning av alla barn, men orsakar i de flesta fall fel. Rekommenderad praxis: anropa super.dispatchDraw(canvas) i början av den åsidosatta metoden och lägg sedan till din egen grafik (t.ex. overlay med alfakanal) ovanpå barnen.
Låt oss skapa OverlayViewGroup — en anpassad ViewGroup som lägger till ett halvgenomskinligt lager med en textetikett ovanpå alla barn-View. dispatchDraw anropar först super (ritning av alla barn), ritar sedan overlay-rektangeln och texten. Paint skapas i konstruktorn för att undvika allokeringar i draw-loopen.
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("FÖRHANDSGRANSKNINGSLÄGE",
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))
}
}
Följande exempel visar ändring av ordningen för ritning av barnelement. CircularRevealLayout åsidosätter dispatchDraw och ritar barnen i omvänd ordning, vilket skapar en effekt av omvänd Z-order. För animering läggs en cyklisk förskjutning av ritningsindex till baserat på det animerade offset-värdet.
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 används i scenarier där du behöver påverka ritningsprocessen för barn-View utan att påverka ViewGroup:s eget innehåll. Huvudscenarier: applicering av en gemensam effekt (overlay, shadow, gradient) ovanpå alla barn; ändring av Z-order-ordning för att skapa en djup-effekt; animering av uppkomst eller försvinnande av barnelement genom Canvas-transformeringar.
Om grafik behöver läggas under barnelement (bakgrundseffekt) ska onDraw användas — dispatchDraw anropas efter det. Om grafiken ska vara ovanpå barnen — använd dispatchDraw med super-anrop och sedan Canvas.draw. Om global färg- eller transparensändring behövs är det bekvämt att använda canvas.saveLayerAlpha() i dispatchDraw som omsluter ritningen av barnen.
Rekommenderas inte att använda dispatchDraw för: 1) ritning av komplex animering i realtid (använd invalidate och onDraw); 2) skapande av skärmdumpar av ViewGroup (använd buildDrawingCache() eller View.draw(Canvas)); 3) modifiering av barn-View (koordinater, storlekar — detta är onLayout:s uppgift, inte dispatchDraw:s). dispatchDraw — endast för applicering av visuella effekter.
Vanliga frågor
Nej, dispatchDraw — protected metod som anropas av Android-systemet inuti den publika metoden draw(). Direkt anrop av dispatchDraw är meningslöst eftersom det inte utför förberedande åtgärder (spara Canvas, rita bakgrund och foreground). Använd istället View.draw(Canvas) för programmatisk ritning i en godtycklig Canvas.
Om du anropar super.dispatchDraw(canvas) efter dina egna ritningsoperationer kommer barn-View att ritas ovanpå den anpassade grafiken. Detta ändrar lageröverlappningen: först ritas det anpassade lagret, sedan barnen. Om den motsatta situationen behövs (grafik ovanpå barnen), anropa först super.dispatchDraw och sedan dina egna operationer.
dispatchDraw anropar draw() för varje barn-View, total tid är proportionell mot antalet barn. Vid hårdvaruacceleration kan tillägg av Canvas-operationer i dispatchDraw leda till ogiltigförklaring av DisplayList och återuppbyggnad av cachen. För 5–10 barn är påverkan minimal, för 50+ barn rekommenderas cachning av komplexa överlägg.
View (inte ViewGroup) har inga barnelement, därför utför dispatchDraw inget användbart arbete. Metoden finns dock i basklassen View för polymorfism: koden i draw() anropar dispatchDraw för vilken View som helst, men implementeringen av View innehåller ingen logik för att rita barn. Endast ViewGroup åsidosätter dispatchDraw.
Ja, dispatchDraw tar emot en Canvas och kan transformeras före anrop av super.dispatchDraw (translate, rotate, scale). Detta tillämpas för att animera hela uppsättningen av barn som en enda enhet. Efter slutförd super.dispatchDraw rekommenderas att Canvas återställs till ursprungligt tillstånd.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också