dispatchDraw() — је метода класе ViewGroup одговорна за рекурзивно цртање свих дете View-ова у Android хијерархији. Према Android Developers Documentation (2026), dispatchDraw се аутоматски позива после onDraw у методи draw() главног View-а и пролази кроз све дете елементе, позивајући њихов сопствени draw(). Програмери преоптерећују dispatchDraw у прилагођеним ViewGroup-овима за додавање ефеката, наношење графике преко деце или промену редоследа цртања.
Главно
dispatchDraw(Canvas canvas) — је protected метода класе ViewGroup коју Android систем позива за цртање свих дете View-ова тренутног контејнера. dispatchDraw је део стандардног draw конвејера: прво се извршава onDraw (цртање садржаја самог View-а), затим dispatchDraw (рекурзивно цртање деце), затим onDrawForeground (цртање foreground-а и scrollbar-ова). Метода није намењена директном позивању из кода апликације.
Стандардна имплементација dispatchDraw-а у ViewGroup-у пролази кроз све дете View-ове, проверава њихову видљивост и позива за сваки draw(Canvas). Редослед обиласка одговара индексима деце (од 0 до childCount - 1). Ако се дете View-ови преклапају, последњи на листи се цртају преко првих. Мењањем редоследа у dispatchDraw-у може се променити Z-order цртања.
За View (не ViewGroup) метода dispatchDraw је празна — обичан View нема дете елемената, па нема шта да се црта. Метода постоји у основној класи View, али се позива само за инстанце ViewGroup-а. Програмер може проверити да ли View има дете елементе путем подразумеваног понашања dispatchDraw-а, али је често једноставније проверити instanceof ViewGroup.
Метода draw(Canvas) класе View организује тростепени цртачки конвејер. Прва фаза — позив onDraw(canvas), у ком View црта свој садржај. Друга фаза — позив dispatchDraw(canvas), који покреће цртање дете View-ова. Трећа фаза — onDrawForeground(canvas), одговоран за foreground слој, scrollbar-ове и ripple ефекте. Ова секвенца гарантује правилан редослед наношења графичких слојева.
onDraw се увек извршава пре dispatchDraw-а. То значи да се садржај родитељског View-а приказује испод дете View-ова. Ако треба нацртати нешто преко деце, то се ради у onDrawForeground-у или у преоптерећеном dispatchDraw-у са позивом super.dispatchDraw(canvas) и накнадним цртањем преко. Позадина (background) се црта још пре onDraw-а — у drawBackground(canvas) унутар методе draw().
При хардверском убрзању (hardware acceleration) draw конвејер ради преко GPU-а. View се црта у DisplayList-у — листи команди цртања која се кешира и поново користи. dispatchDraw у хардверски убрзаном режиму додаје DisplayList дете View-ова у општи DisplayList сцене. Промена dispatchDraw-а може довести до инвалидације DisplayList-а и поновне изградње кеша, што утиче на перформансе.
| Метода | Редослед позива | Намена |
|---|---|---|
| drawBackground | 1 | Цртање позадине (background drawable) |
| onDraw | 2 | Цртање садржаја самог View-а |
| dispatchDraw | 3 | Цртање свих дете View-ова (само за ViewGroup) |
| onDrawForeground | 4 | Цртање foreground-а, scrollbar-а и ripple ефекта |
onDraw је одговоран за цртање садржаја самог View-а — облика, текста, слика које припадају овом конкретном елементу. dispatchDraw је одговоран за цртање дете View-ова — свих елемената који се налазе унутар ViewGroup-а. Ако ViewGroup не преоптерећује dispatchDraw, користи се имплементација из ViewGroup-а која једноставно рекурзивно пролази кроз childCount и позива draw за свако дете.
За ViewGroup која не црта сопствени садржај (нпр. FrameLayout, LinearLayout), onDraw се може оптимизовати постављањем setWillNotDraw(true). У овом случају onDraw се никада не позива, што штеди ресурсе. dispatchDraw притом наставља да ради и позива draw за дете елементе. Сви стандардни ViewGroup-ови (LinearLayout, RelativeLayout, ConstraintLayout) користе setWillNotDraw(true).
Ако се dispatchDraw преоптерети без позива super.dispatchDraw(canvas), дете View-ови неће бити нацртани. Ово може бити корисно за привремено скривање све деце, али у већини случајева изазива грешке. Препоручена пракса: позвати super.dispatchDraw(canvas) на почетку преоптерећене методе, а затим додати своју графику (нпр. overlay са алфа каналом) преко деце.
Направимо OverlayViewGroup — прилагођени ViewGroup који додаје полупровидан слој са текстуалном ознаком преко свих дете View-ова. dispatchDraw прво позива super (цртање све деце), затим црта overlay правоугаоник и текст. Paint се креира у конструктору ради избегавања алокација у 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("PREGLED NAČIN",
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))
}
}
Следећи пример демонстрира промену редоследа цртања дете елемената. CircularRevealLayout преоптерећује dispatchDraw и црта децу обрнутим редом, стварајући ефекат обрнутог Z-order-а. За анимацију се додаје циклични помак индекса цртања на основу анимиране вредности 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 се користи у сценаријима где треба утицати на процес цртања дете View-ова без утицаја на сопствени садржај ViewGroup-а. Главни сценарији: наношење заједничког ефекта (overlay, shadow, gradient) преко све деце; промена Z-order редоследа за стварање ефекта дубине; анимација појављивања или нестајања дете елемената кроз Canvas трансформације.
Ако је потребно наношење графике испод дете елемената (ефекат позадине), треба користити onDraw — dispatchDraw се позива после њега. Ако графика треба да буде преко деце — користите dispatchDraw са позивом super, а затим Canvas.draw. Ако је потребна глобална промена боје или провидности, згодно је користити canvas.saveLayerAlpha() у dispatchDraw-у, обавијајући цртање деце.
Не препоручује се коришћење dispatchDraw-а за: 1) цртање сложене анимације у реалном времену (користите invalidate и onDraw); 2) прављење снимака екрана ViewGroup-а (користите buildDrawingCache() или View.draw(Canvas)); 3) модификацију дете View-ова (координате, димензије — то је задатак onLayout-а, не dispatchDraw-а). dispatchDraw — само за наношење визуелних ефеката.
Често постављана питања
Не, dispatchDraw — protected метода коју позива Android систем унутар јавне методе draw(). Директан позив dispatchDraw-а нема смисла, јер не обавља припремне радње (чување Canvas-а, цртање позадине и foreground-а). Уместо тога користите View.draw(Canvas) за програмско цртање у произвољни Canvas.
Ако позовете super.dispatchDraw(canvas) после сопствених операција цртања, дете View-ови ће бити нацртани преко прилагођене графике. Ово мења наношење слојева: прво се црта прилагођени слој, затим деца. Ако је потребна обрнута ситуација (графика преко деце), прво позовите super.dispatchDraw, затим своје операције.
dispatchDraw позива draw() за свако дете View, укупно време је пропорционално броју деце. При hardware acceleration додавање Canvas операција у dispatchDraw може довести до инвалидације DisplayList-а и поновне изградње кеша. За 5–10 деце утицај је минималан, за 50+ деце препоручује се кеширање сложених наношења.
View (не ViewGroup) нема дете елемената, зато dispatchDraw не обавља користан посао. Међутим, метода постоји у основној класи View ради полиморфизма: код у draw() позива dispatchDraw за било који View, али имплементација View-а не садржи логику цртања деце. Само ViewGroup преоптерећује dispatchDraw.
Да, dispatchDraw прима Canvas и може се трансформисати пре позива super.dispatchDraw (translate, rotate, scale). Ово се примењује за анимацију целог скупа деце као јединствене целине. По завршетку super.dispatchDraw препоручује се враћање Canvas-а у првобитно стање.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође