dispatchDraw(): шта је то, механизам позива и разлика од onDraw

Аутор: IT Sectr Објављено: 2026-07-22 Време читања: 9 мин

dispatchDraw() — је метода класе ViewGroup одговорна за рекурзивно цртање свих дете View-ова у Android хијерархији. Према Android Developers Documentation (2026), dispatchDraw се аутоматски позива после onDraw у методи draw() главног View-а и пролази кроз све дете елементе, позивајући њихов сопствени draw(). Програмери преоптерећују dispatchDraw у прилагођеним ViewGroup-овима за додавање ефеката, наношење графике преко деце или промену редоследа цртања.

Главно

  • dispatchDraw(Canvas canvas) — protected метода ViewGroup-а позвана после onDraw за цртање дете View-ова
  • draw(Canvas canvas) — public final метода View-а која узастопно позива onDraw, dispatchDraw и onDrawForeground
  • onDraw црта садржај самог View-а, а dispatchDraw — садржај свих дете елемената
  • setWillNotDraw(true) — оптимизација за ViewGroup која не црта сопствени садржај, већ само дете елементе
  • Преоптерећење dispatchDraw-а се користи за наношење ефеката (blur, overlay) или анимацију цртања дете елемената

Шта је dispatchDraw()?

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.

Цртачки конвејер: onDraw, dispatchDraw, onDrawForeground

Метода 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-а и поновне изградње кеша, што утиче на перформансе.

МетодаРедослед позиваНамена
drawBackground1Цртање позадине (background drawable)
onDraw2Цртање садржаја самог View-а
dispatchDraw3Цртање свих дете View-ова (само за ViewGroup)
onDrawForeground4Цртање foreground-а, scrollbar-а и ripple ефекта

Чему се разликује dispatchDraw од onDraw-а

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 са алфа каналом) преко деце.

Пример преоптерећења dispatchDraw-а у Kotlin-у

Направимо OverlayViewGroup — прилагођени ViewGroup који додаје полупровидан слој са текстуалном ознаком преко свих дете View-ова. dispatchDraw прво позива super (цртање све деце), затим црта overlay правоугаоник и текст. Paint се креира у конструктору ради избегавања алокација у 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("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))
    }
}

Анимирани Z-order кроз dispatchDraw

Следећи пример демонстрира промену редоследа цртања дете елемената. CircularRevealLayout преоптерећује dispatchDraw и црта децу обрнутим редом, стварајући ефекат обрнутог Z-order-а. За анимацију се додаје циклични помак индекса цртања на основу анимиране вредности 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))
    }
}

Када користити dispatchDraw у Android-у

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 позвати директно без система?

Не, dispatchDraw — protected метода коју позива Android систем унутар јавне методе draw(). Директан позив dispatchDraw-а нема смисла, јер не обавља припремне радње (чување Canvas-а, цртање позадине и foreground-а). Уместо тога користите View.draw(Canvas) за програмско цртање у произвољни Canvas.

Шта се дешава при позиву super.dispatchDraw после прилагођеног цртања?

Ако позовете super.dispatchDraw(canvas) после сопствених операција цртања, дете View-ови ће бити нацртани преко прилагођене графике. Ово мења наношење слојева: прво се црта прилагођени слој, затим деца. Ако је потребна обрнута ситуација (графика преко деце), прво позовите super.dispatchDraw, затим своје операције.

Како dispatchDraw утиче на перформансе?

dispatchDraw позива draw() за свако дете View, укупно време је пропорционално броју деце. При hardware acceleration додавање Canvas операција у dispatchDraw може довести до инвалидације DisplayList-а и поновне изградње кеша. За 5–10 деце утицај је минималан, за 50+ деце препоручује се кеширање сложених наношења.

Зашто dispatchDraw није позван за View?

View (не ViewGroup) нема дете елемената, зато dispatchDraw не обавља користан посао. Међутим, метода постоји у основној класи View ради полиморфизма: код у draw() позива dispatchDraw за било који View, али имплементација View-а не садржи логику цртања деце. Само ViewGroup преоптерећује dispatchDraw.

Може ли се у dispatchDraw-у изменити Canvas?

Да, dispatchDraw прима Canvas и може се трансформисати пре позива super.dispatchDraw (translate, rotate, scale). Ово се примењује за анимацију целог скупа деце као јединствене целине. По завршетку super.dispatchDraw препоручује се враћање Canvas-а у првобитно стање.

Резиме

  • dispatchDraw(Canvas) — метода ViewGroup-а за рекурзивно цртање свих дете View-ова после onDraw-а
  • Draw конвејер: drawBackground → onDraw → dispatchDraw → onDrawForeground
  • onDraw црта садржај самог View-а, dispatchDraw — садржај дете елемената
  • setWillNotDraw(true) оптимизује ViewGroup, прескачући onDraw, али dispatchDraw наставља да ради
  • Преоптерећење dispatchDraw-а се користи за наношење ефеката, промену Z-order-а и Canvas трансформације деце
  • Редослед super.dispatchDraw и прилагођеног цртања одређује шта ће бити преко деце
  • За обичан View (не ViewGroup) dispatchDraw је празан — нема дете елемената

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође