onDraw() — είναι η βασική μέθοδος της κλάσης View στο Android SDK, υπεύθυνη για την απόδοση του περιεχομένου της προβολής. Το σύστημα καλεί την onDraw σε κάθε ενημέρωση της εμφάνισης του View — κατά την αρχική απόδοση, μετά από invalidate() ή κατά την αλλαγή διαστάσεων. Σύμφωνα με το Android Developers Documentation (2026), η παράκαμψη της onDraw με χρήση του Canvas API είναι ο κύριος τρόπος δημιουργίας προσαρμοσμένων γραφικών σε εφαρμογές Android.
Κύρια σημεία
onDraw(Canvas canvas) — είναι μια protected μέθοδος της κλάσης android.view.View, η οποία καλείται από το σύστημα Android όταν το View πρέπει να αποδοθεί. Ο προγραμματιστής παρακάμπτει αυτή τη μέθοδο για να υλοποιήσει προσαρμοσμένη απόδοση: σχήματα, κείμενο, εικόνες, διαβαθμίσεις, κινούμενα σχέδια. Η παράμετρος Canvas είναι η γραφική επιφάνεια στην οποία εκτελούνται όλες οι λειτουργίες σχεδίασης.
Η υπογραφή της μεθόδου είναι απλή: override fun onDraw(canvas: Canvas). Μέσα σε αυτή τη μέθοδο δεν μπορούν να δημιουργηθούν νέα αντικείμενα (για αποφυγή φόρτου GC), να εκτελεστούν μεγάλες λειτουργίες ή να γίνει εργασία με δίκτυο. Το View περιέχει επίσης τη μέθοδο onDraw στον κύκλο ζωής του μεταξύ onLayout (υπολογισμός θέσεων) και dispatchDraw (απόδοση θυγατρικών στοιχείων).
Κατά την κληρονόμηση από το View, η παράκαμψη της onDraw είναι υποχρεωτική για την εμφάνιση οποιουδήποτε περιεχομένου — διαφορετικά το View θα είναι κενό. Κατά την κληρονόμηση από υπάρχουσες υποκλάσεις (TextView, ImageView, Button), η παράκαμψη της onDraw είναι προαιρετική και χρησιμοποιείται για την προσθήκη επιπλέον γραφικών πάνω από το τυπικό περιεχόμενο.
Android εκτελεί την απόδοση του View σε αυστηρά καθορισμένη σειρά, η οποία καλείται από το κύριο νήμα (UI thread). Ο πλήρης κύκλος αποτελείται από τρεις φάσεις: measure (μέτρηση διαστάσεων), layout (υπολογισμός θέσεων) και draw (απόδοση). Η onDraw είναι μέρος της φάσης draw και καλείται αφού καθοριστούν οι διαστάσεις και η θέση του View.
draw(Canvas canvas) — είναι η public μέθοδος του View που καλεί την onDraw και την dispatchDraw. Η μέθοδος draw δεν προορίζεται για παράκαμψη — αντί αυτής παρακάμπτεται η onDraw. Μετά την εκτέλεση της onDraw για το τρέχον View, καλείται η dispatchDraw, η οποία αναδρομικά καλεί την draw για όλα τα θυγατρικά View (αν το τρέχον View είναι κοντέινερ ViewGroup).
onDraw καλείται υπό τις ακόλουθες συνθήκες: 1) το View γίνεται ορατό για πρώτη φορά (μετά από setVisibility(VISIBLE)); 2) η εφαρμογή καλεί invalidate() για αυτό το View ή τον γονέα του; 3) αλλάζει το μέγεθος του View (νέο layout); 4) αλλάζει το κινούμενο σχέδιο που σχετίζεται με το View (μέσω ValueAnimator ή ObjectAnimator); 5) αλλάζει η κατάσταση του View (pressed, focused, enabled).
Από Android 3.0 (API 11), οι περισσότερες λειτουργίες Canvas εκτελούνται με επιτάχυνση υλικού μέσω GPU. Ο προγραμματιστής μπορεί να ελέγξει αν η επιτάχυνση είναι ενεργοποιημένη μέσω canvas.isHardwareAccelerated(). Με επιτάχυνση υλικού, ορισμένες λειτουργίες Canvas δεν υποστηρίζονται (π.χ. clipPath με μη ορθογώνια περιοχή) και μπορεί να αγνοηθούν ή να προκαλέσουν σφάλματα.
Canvas παρέχει ένα πλούσιο σύνολο μεθόδων για σχεδίαση. Όλες οι μέθοδοι λειτουργούν στο τρέχον σύστημα συντεταγμένων, το οποίο μπορεί να τροποποιηθεί μέσω μετασχηματισμών (translate, rotate, scale, skew). Κάθε μέθοδος δέχεται ένα αντικείμενο Paint που καθορίζει το χρώμα, το στυλ, το πάχος γραμμής, τα εφέ και την εξομάλυνση.
| Μέθοδος Canvas | Σκοπός | Βασικές παράμετροι Paint |
|---|---|---|
| drawCircle | Σχεδίαση κύκλου | centerX, centerY, radius, paint |
| drawRect | Σχεδίαση ορθογωνίου | left, top, right, bottom, paint |
| drawLine | Σχεδίαση γραμμής | startX, startY, endX, endY, paint |
| drawPath | Σχεδίαση αυθαίρετου περιγράμματος | path, paint |
| drawBitmap | Σχεδίαση εικόνας | bitmap, srcRect, dstRect, paint |
| drawText | Σχεδίαση κειμένου | text, x, y, paint |
| drawArc | Σχεδίαση τόξου ή τομέα | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Σχεδίαση ωοειδούς | ovalRect, paint |
Paint καθορίζει το στυλ απόδοσης: χρώμα (setColor), πάχος γραμμής (setStrokeWidth), στυλ γεμίσματος (setStyle — FILL, STROKE, FILL_AND_STROKE), εξομάλυνση (setAntiAlias), εφέ (setShadowLayer, setMaskFilter) και διαβαθμίσεις (setShader). Συνιστάται η δημιουργία και διαμόρφωση του Paint μία φορά στον κατασκευαστή του View, όχι μέσα στην onDraw, για να αποφευχθεί η δημιουργία αντικειμένων στον κύκλο απόδοσης.
Canvas χρησιμοποιεί ένα σύστημα συντεταγμένων όπου ο άξονας Χ είναι προς τα δεξιά και ο άξονας Υ προς τα κάτω. Η αρχή των συντεταγμένων (0, 0) είναι η πάνω αριστερή γωνία του View. Πριν από την κλήση των μεθόδων draw, ο προγραμματιστής μπορεί να αλλάξει το σύστημα συντεταγμένων μέσω canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). Όλες οι επόμενες λειτουργίες σχεδίασης θα εκτελεστούν στο μετασχηματισμένο σύστημα.
onDraw εκτελείται στο κύριο νήμα UI και η διάρκειά του επηρεάζει άμεσα τον ρυθμό καρέ (fps). Για τη διατήρηση 60 fps, η μέθοδος onDraw πρέπει να ολοκληρωθεί εντός το πολύ 16 χιλιοστών του δευτερολέπτου. Οποιαδήποτε επιβράδυνση οδηγεί σε παράλειψη καρέ (jank), η οποία γίνεται αντιληπτή οπτικά ως τραύλισμα της κινούμενης εικόνας ή καθυστέρηση κατά την κύλιση.
Οι βασικοί κανόνες βελτιστοποίησης της onDraw περιλαμβάνουν: 1) μην δημιουργείτε αντικείμενα μέσα στην onDraw — όλα τα Paint, Path, Rect πρέπει να είναι πεδία της κλάσης; 2) μην δεσμεύετε μνήμη (new) — αυτό ενεργοποιεί τη συλλογή σκουπιδιών (GC) και την παράλειψη καρέ; 3) αποθηκεύστε προσωρινά σύνθετες λειτουργίες σε Bitmap μέσω Bitmap.createBitmap και σχεδιάστε το έτοιμο Bitmap; 4) μην εκτελείτε λειτουργίες εισόδου-εξόδου ή φόρτωσης πόρων.
clipRect περιορίζει την περιοχή σχεδίασης σε ένα καθορισμένο ορθογώνιο. Χρησιμοποιείται για να μην σπαταλά το Canvas χρόνο στην απόδοση εικονοστοιχείων εκτός της ορατής περιοχής. Ιδιαίτερα αποτελεσματικό κατά την κύλιση μεγάλων λιστών ή τη μεγέθυνση: ορίστε το clipRect στην ορατή περιοχή (dirty rect) και το Canvas θα σχεδιάζει μόνο ό,τι είναι ορατό.
canvas.save() αποθηκεύει την τρέχουσα κατάσταση του Canvas (μετασχηματισμό και clip) στη στοίβα. canvas.restore() επαναφέρει την τελευταία αποθηκευμένη κατάσταση. Αυτό είναι απαραίτητο για προσωρινούς μετασχηματισμούς: αποθηκεύετε την κατάσταση, εφαρμόζετε translate/rotate, σχεδιάζετε, στη συνέχεια επαναφέρετε. Τα save και restore είναι ελαφριά και δεν επηρεάζουν την απόδοση με λογική χρήση (λιγότερα από 10–15 επίπεδα ένθεσης).
invalidate() — είναι η μέθοδος View που ειδοποιεί το σύστημα ότι το περιεχόμενο του View είναι παρωχημένο και πρέπει να επανασχεδιαστεί. Η κλήση invalidate ξεκινά έναν νέο κύκλο draw, στον οποίο θα κληθεί η onDraw. Η invalidate πρέπει να καλείται μόνο από το νήμα UI (main thread). Για κλήση από νήμα παρασκηνίου χρησιμοποιείται η postInvalidate.
postInvalidate() — η ασφαλής για νήματα έκδοση της invalidate, η οποία στέλνει το αίτημα επανασχεδίασης στην ουρά του κύριου νήματος μέσω Handler. Χρησιμοποιείται σε εργασίες παρασκηνίου όταν χρειάζεται να ενημερωθεί το View μετά την ολοκλήρωση φόρτωσης δεδομένων ή υπολογισμών. Συνδυάζεται με runOnUiThread για συγχρονισμό με το UI.
Για μερική επανασχεδίαση χρησιμοποιείται invalidate(Rect dirty) ή invalidate(int l, int t, int r, int b). Αυτό υποδεικνύει στο σύστημα ότι έχει αλλάξει μόνο η καθορισμένη περιοχή, όχι ολόκληρο το View. Το Android βελτιστοποιεί την απόδοση, επανασχεδιάζοντας μόνο το dirty rect, γεγονός που βελτιώνει σημαντικά την απόδοση σε κινούμενα σχέδια με μερικές ενημερώσεις.
Ας εξετάσουμε πρακτικά παραδείγματα παράκαμψης της onDraw για δημιουργία προσαρμοσμένων View σε Android με Kotlin.
Το παράδειγμα δημιουργεί έναν κυκλικό δείκτη προόδου με κινούμενο γέμισμα. Η onDraw σχεδιάζει δύο κύκλους: ένα γκρι φόντο και ένα χρωματιστό γέμισμα, του οποίου το τόξο καθορίζεται από την τρέχουσα πρόοδο. Η κλήση invalidate στο setProgress εξασφαλίζει επανασχεδίαση όταν αλλάζει η τιμή.
class CircularProgressView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val bgPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 12f
color = Color.parseColor("#E0E0E0")
isAntiAlias = true
}
private val progressPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 12f
color = Color.BLUE
isAntiAlias = true
strokeCap = Paint.Cap.ROUND
}
var progress: Float = 0f
set(value) {
field = value.coerceIn(0f, 1f)
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val size = minOf(width, height) - 24f
val oval = RectF(12f, 12f,
12f + size, 12f + size)
canvas.drawArc(oval, 270f, 360f, false, bgPaint)
canvas.drawArc(oval, 270f,
progress * 360f,
false, progressPaint)
}
}
Το παράδειγμα σχεδιάζει ένα ορθογώνιο με διαβάθμιση και κείμενο πάνω του. Η διαβάθμιση δημιουργείται μέσω LinearGradient (Shader) και το κείμενο μέσω drawText. Και τα δύο Paint δημιουργούνται μία φορά στον κατασκευαστή και στην onDraw αλλάζει μόνο το μέγεθος του ορθογωνίου ανάλογα με το τρέχον πλάτος του View.
class GradientTextView(context: Context)
: View(context) {
private val shaderPaint = Paint().apply {
isAntiAlias = true
}
private val textPaint = Paint().apply {
color = Color.WHITE
textSize = 48f
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val gradient = LinearGradient(
0f, 0f, width.toFloat(), 0f,
Color.parseColor("#FF6B35"),
Color.parseColor("#FF0040"),
Shader.TileMode.CLAMP
)
shaderPaint.shader = gradient
canvas.drawRect(0f, 0f,
width.toFloat(),
height.toFloat(), shaderPaint)
val xCenter = width / 2f
val yCenter = height / 2f
val yOffset = (textPaint.descent() +
textPaint.ascent()) / 2f
canvas.drawText("Γεια από το Canvas",
xCenter, yCenter - yOffset,
textPaint)
}
}
Το παράδειγμα δημιουργεί ένα κινούμενο ρολόι που ενημερώνει την απόδοση κάθε 1000 χιλιοστά του δευτερολέπτου. Η postDelayed στην onDraw καλεί invalidate μετά από ένα δευτερόλεπτο, δημιουργώντας έναν ατελείωτο κύκλο επανασχεδίασης. Οι δείκτες του ρολογιού σχεδιάζονται μέσω drawLine με διαφορετικό μήκος και πάχος.
class AnalogClockView(context: Context)
: View(context) {
private val handPaint = Paint().apply {
style = Paint.Style.STROKE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 20f
val now = Calendar.getInstance()
val hours = now.get(Calendar.HOUR) % 12
val minutes = now.get(Calendar.MINUTE)
val seconds = now.get(Calendar.SECOND)
val secondAngle = Math.toRadians(
seconds * 6f - 90f)
handPaint.strokeWidth = 2f
handPaint.color = Color.RED
canvas.drawLine(cx, cy,
cx + cos(secondAngle) * radius * 0.8f,
cy + sin(secondAngle) * radius * 0.8f,
handPaint)
postDelayed({ invalidate() }, 1000)
}
}
Συχνές ερωτήσεις
Όχι, δεν πρέπει να καλείτε την onDraw απευθείας. Για αίτημα επανασχεδίασης χρησιμοποιήστε invalidate() (από το νήμα UI) ή postInvalidate() (από το νήμα παρασκηνίου). Το σύστημα αποφασίζει πότε θα καλέσει την onDraw και βελτιστοποιεί την απόδοση, συνδυάζοντας πολλαπλές invalidate σε ένα πέρασμα.
Αιτίες: το View είναι κρυμμένο (setVisibility(GONE) ή INVISIBLE); το View δεν έχει προστεθεί στην ιεραρχία του Window; το μέγεθος του View είναι 0 (width ή height = 0); καλείται setWillNotDraw(true) (προεπιλογή true για ViewGroup). Ελέγξτε κάθε ένα από αυτά τα σημεία κατά τον εντοπισμό σφαλμάτων.
Στα 60 fps, η onDraw μπορεί να κληθεί έως και 60 φορές το δευτερόλεπτο. Στα 120 fps (σε συσκευές με οθόνες 120 Hz) — έως 120 φορές. Κάθε κλήση πρέπει να ολοκληρώνεται εντός ~8–16 χιλιοστών του δευτερολέπτου. Σε ενεργά κινούμενα σχέδια μέσω ObjectAnimator ή ValueAnimator, η συχνότητα καθορίζεται από τον αριθμό των καρέ κινούμενου σχεδίου.
onDraw είναι υπεύθυνη για την απόδοση του περιεχομένου του ίδιου του View. dispatchDraw είναι υπεύθυνη για την απόδοση των θυγατρικών View (σε ViewGroup). Η dispatchDraw καλείται μετά την onDraw και διατρέχει αναδρομικά όλα τα θυγατρικά στοιχεία. Για ένα συνηθισμένο View (όχι ViewGroup), η dispatchDraw είναι κενή.
Ναι, το Canvas μπορεί να δημιουργηθεί για Bitmap μέσω Canvas(bitmap) και να σχεδιάζεται σε οποιοδήποτε νήμα. Αυτό χρησιμοποιείται για προ-υπολογισμό σύνθετων γραφικών στο παρασκήνιο. Ωστόσο, το Canvas που μεταβιβάζεται στην onDraw ανήκει στο σύστημα και μπορεί να χρησιμοποιηθεί μόνο στο νήμα UI εντός της μεθόδου onDraw.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης