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 και scrollbars). Η μέθοδος δεν προορίζεται για άμεση κλήση από τον κώδικα εφαρμογής.
Η τυπική υλοποίηση του 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, τις γραμμές κύλισης και τα εφέ 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, γραμμής κύλισης και εφέ 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("ΛΕΙΤΟΥΡΓΙΑ ΠΡΟΕΠΙΣΚΟΠΗΣΗΣ",
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, ο συνολικός χρόνος είναι ανάλογος του αριθμού των παιδιών. Σε επιτάχυνση υλικού, η προσθήκη λειτουργιών 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης