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 και scrollbars). Η μέθοδος δεν προορίζεται για άμεση κλήση από τον κώδικα εφαρμογής.

Η τυπική υλοποίηση του 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, τις γραμμές κύλισης και τα εφέ 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, γραμμής κύλισης και εφέ 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("ΛΕΙΤΟΥΡΓΙΑ ΠΡΟΕΠΙΣΚΟΠΗΣΗΣ",
            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, ο συνολικός χρόνος είναι ανάλογος του αριθμού των παιδιών. Σε επιτάχυνση υλικού, η προσθήκη λειτουργιών Canvas στο dispatchDraw μπορεί να οδηγήσει σε ακύρωση του DisplayList και ανακατασκευή της προσωρινής μνήμης. Για 5–10 παιδιά η επίδραση είναι ελάχιστη, για 50+ παιδιά συνιστάται η προσωρινή αποθήκευση σύνθετων επικαλύψεων.

Γιατί το dispatchDraw δεν καλείται για View;

View (όχι ViewGroup) δεν έχει θυγατρικά στοιχεία, επομένως το dispatchDraw δεν εκτελεί χρήσιμη εργασία. Ωστόσο, η μέθοδος υπάρχει στη βασική κλάση View για πολυμορφισμό: ο κώδικας στο draw() καλεί το dispatchDraw για οποιοδήποτε View, αλλά η υλοποίηση του View δεν περιέχει λογική σχεδίασης παιδιών. Μόνο το ViewGroup παρακάμπτει το dispatchDraw.

Μπορεί το Canvas να αλλάξει στο dispatchDraw;

Ναι, το 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης