onDraw() — τι είναι, μηχανισμός κλήσης και παρακάμψη της μεθόδου

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-07-22 Χρόνος ανάγνωσης: 10 λεπ

onDraw() — είναι η βασική μέθοδος της κλάσης View στο Android SDK, υπεύθυνη για την απόδοση του περιεχομένου της προβολής. Το σύστημα καλεί την onDraw σε κάθε ενημέρωση της εμφάνισης του View — κατά την αρχική απόδοση, μετά από invalidate() ή κατά την αλλαγή διαστάσεων. Σύμφωνα με το Android Developers Documentation (2026), η παράκαμψη της onDraw με χρήση του Canvas API είναι ο κύριος τρόπος δημιουργίας προσαρμοσμένων γραφικών σε εφαρμογές Android.

Κύρια σημεία

  • onDraw(Canvas) — μέθοδος View που καλείται από το σύστημα για απόδοση περιεχομένου στο Canvas
  • invalidate() — αίτημα επανασχεδίασης του View, που οδηγεί σε κλήση της onDraw στον επόμενο κύκλο καρέ
  • Canvas API παρέχει μεθόδους drawCircle, drawBitmap, drawText, drawPath και άλλες για σχεδίαση
  • Το σύστημα Android εκτελεί την απόδοση στο νήμα UI, επομένως η onDraw πρέπει να είναι ελαφριά και γρήγορη
  • Hardware acceleration επιταχύνει αυτόματα την απόδοση μέσω GPU, ξεκινώντας από Android 3.0 (API 11)

Τι είναι το onDraw();

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 είναι προαιρετική και χρησιμοποιείται για την προσθήκη επιπλέον γραφικών πάνω από το τυπικό περιεχόμενο.

Κύκλος απόδοσης View

Android εκτελεί την απόδοση του View σε αυστηρά καθορισμένη σειρά, η οποία καλείται από το κύριο νήμα (UI thread). Ο πλήρης κύκλος αποτελείται από τρεις φάσεις: measure (μέτρηση διαστάσεων), layout (υπολογισμός θέσεων) και draw (απόδοση). Η onDraw είναι μέρος της φάσης draw και καλείται αφού καθοριστούν οι διαστάσεις και η θέση του View.

Φάση draw: dispatchDraw και onDraw

draw(Canvas canvas) — είναι η public μέθοδος του View που καλεί την onDraw και την dispatchDraw. Η μέθοδος draw δεν προορίζεται για παράκαμψη — αντί αυτής παρακάμπτεται η onDraw. Μετά την εκτέλεση της onDraw για το τρέχον View, καλείται η dispatchDraw, η οποία αναδρομικά καλεί την draw για όλα τα θυγατρικά View (αν το τρέχον View είναι κοντέινερ ViewGroup).

Πότε το σύστημα καλεί την onDraw

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 API για προσαρμοσμένα γραφικά

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

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

onDraw εκτελείται στο κύριο νήμα UI και η διάρκειά του επηρεάζει άμεσα τον ρυθμό καρέ (fps). Για τη διατήρηση 60 fps, η μέθοδος onDraw πρέπει να ολοκληρωθεί εντός το πολύ 16 χιλιοστών του δευτερολέπτου. Οποιαδήποτε επιβράδυνση οδηγεί σε παράλειψη καρέ (jank), η οποία γίνεται αντιληπτή οπτικά ως τραύλισμα της κινούμενης εικόνας ή καθυστέρηση κατά την κύλιση.

Κανόνες για αποδοτική onDraw

Οι βασικοί κανόνες βελτιστοποίησης της onDraw περιλαμβάνουν: 1) μην δημιουργείτε αντικείμενα μέσα στην onDraw — όλα τα Paint, Path, Rect πρέπει να είναι πεδία της κλάσης; 2) μην δεσμεύετε μνήμη (new) — αυτό ενεργοποιεί τη συλλογή σκουπιδιών (GC) και την παράλειψη καρέ; 3) αποθηκεύστε προσωρινά σύνθετες λειτουργίες σε Bitmap μέσω Bitmap.createBitmap και σχεδιάστε το έτοιμο Bitmap; 4) μην εκτελείτε λειτουργίες εισόδου-εξόδου ή φόρτωσης πόρων.

Χρήση clipRect για βελτιστοποίηση

clipRect περιορίζει την περιοχή σχεδίασης σε ένα καθορισμένο ορθογώνιο. Χρησιμοποιείται για να μην σπαταλά το Canvas χρόνο στην απόδοση εικονοστοιχείων εκτός της ορατής περιοχής. Ιδιαίτερα αποτελεσματικό κατά την κύλιση μεγάλων λιστών ή τη μεγέθυνση: ορίστε το clipRect στην ορατή περιοχή (dirty rect) και το Canvas θα σχεδιάζει μόνο ό,τι είναι ορατό.

Layer — αποθήκευση και επαναφορά

canvas.save() αποθηκεύει την τρέχουσα κατάσταση του Canvas (μετασχηματισμό και clip) στη στοίβα. canvas.restore() επαναφέρει την τελευταία αποθηκευμένη κατάσταση. Αυτό είναι απαραίτητο για προσωρινούς μετασχηματισμούς: αποθηκεύετε την κατάσταση, εφαρμόζετε translate/rotate, σχεδιάζετε, στη συνέχεια επαναφέρετε. Τα save και restore είναι ελαφριά και δεν επηρεάζουν την απόδοση με λογική χρήση (λιγότερα από 10–15 επίπεδα ένθεσης).

invalidate και postInvalidate

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, γεγονός που βελτιώνει σημαντικά την απόδοση σε κινούμενα σχέδια με μερικές ενημερώσεις.

Παραδείγματα κώδικα σε Kotlin

Ας εξετάσουμε πρακτικά παραδείγματα παράκαμψης της onDraw για δημιουργία προσαρμοσμένων View σε Android με Kotlin.

Προσαρμοσμένο View — κυκλική πρόοδος

Το παράδειγμα δημιουργεί έναν κυκλικό δείκτη προόδου με κινούμενο γέμισμα. Η onDraw σχεδιάζει δύο κύκλους: ένα γκρι φόντο και ένα χρωματιστό γέμισμα, του οποίου το τόξο καθορίζεται από την τρέχουσα πρόοδο. Η κλήση invalidate στο setProgress εξασφαλίζει επανασχεδίαση όταν αλλάζει η τιμή.

kotlin
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)
    }
}

Προσαρμοσμένο View — φόντο με διαβάθμιση και κείμενο

Το παράδειγμα σχεδιάζει ένα ορθογώνιο με διαβάθμιση και κείμενο πάνω του. Η διαβάθμιση δημιουργείται μέσω LinearGradient (Shader) και το κείμενο μέσω drawText. Και τα δύο Paint δημιουργούνται μία φορά στον κατασκευαστή και στην onDraw αλλάζει μόνο το μέγεθος του ορθογωνίου ανάλογα με το τρέχον πλάτος του View.

kotlin
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)
    }
}

Κινούμενο ρολόι με χρήση invalidate

Το παράδειγμα δημιουργεί ένα κινούμενο ρολόι που ενημερώνει την απόδοση κάθε 1000 χιλιοστά του δευτερολέπτου. Η postDelayed στην onDraw καλεί invalidate μετά από ένα δευτερόλεπτο, δημιουργώντας έναν ατελείωτο κύκλο επανασχεδίασης. Οι δείκτες του ρολογιού σχεδιάζονται μέσω drawLine με διαφορετικό μήκος και πάχος.

kotlin
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 απευθείας;

Όχι, δεν πρέπει να καλείτε την onDraw απευθείας. Για αίτημα επανασχεδίασης χρησιμοποιήστε invalidate() (από το νήμα UI) ή postInvalidate() (από το νήμα παρασκηνίου). Το σύστημα αποφασίζει πότε θα καλέσει την onDraw και βελτιστοποιεί την απόδοση, συνδυάζοντας πολλαπλές invalidate σε ένα πέρασμα.

Γιατί δεν καλείται η onDraw μετά από invalidate;

Αιτίες: το View είναι κρυμμένο (setVisibility(GONE) ή INVISIBLE); το View δεν έχει προστεθεί στην ιεραρχία του Window; το μέγεθος του View είναι 0 (width ή height = 0); καλείται setWillNotDraw(true) (προεπιλογή true για ViewGroup). Ελέγξτε κάθε ένα από αυτά τα σημεία κατά τον εντοπισμό σφαλμάτων.

Πόσο συχνά μπορεί να κληθεί η onDraw;

Στα 60 fps, η onDraw μπορεί να κληθεί έως και 60 φορές το δευτερόλεπτο. Στα 120 fps (σε συσκευές με οθόνες 120 Hz) — έως 120 φορές. Κάθε κλήση πρέπει να ολοκληρώνεται εντός ~8–16 χιλιοστών του δευτερολέπτου. Σε ενεργά κινούμενα σχέδια μέσω ObjectAnimator ή ValueAnimator, η συχνότητα καθορίζεται από τον αριθμό των καρέ κινούμενου σχεδίου.

Ποια είναι η διαφορά μεταξύ onDraw και dispatchDraw;

onDraw είναι υπεύθυνη για την απόδοση του περιεχομένου του ίδιου του View. dispatchDraw είναι υπεύθυνη για την απόδοση των θυγατρικών View (σε ViewGroup). Η dispatchDraw καλείται μετά την onDraw και διατρέχει αναδρομικά όλα τα θυγατρικά στοιχεία. Για ένα συνηθισμένο View (όχι ViewGroup), η dispatchDraw είναι κενή.

Μπορεί να χρησιμοποιηθεί το Canvas εκτός onDraw;

Ναι, το Canvas μπορεί να δημιουργηθεί για Bitmap μέσω Canvas(bitmap) και να σχεδιάζεται σε οποιοδήποτε νήμα. Αυτό χρησιμοποιείται για προ-υπολογισμό σύνθετων γραφικών στο παρασκήνιο. Ωστόσο, το Canvas που μεταβιβάζεται στην onDraw ανήκει στο σύστημα και μπορεί να χρησιμοποιηθεί μόνο στο νήμα UI εντός της μεθόδου onDraw.

Σύνοψη

  • onDraw(Canvas) — βασική μέθοδος View για προσαρμοσμένη απόδοση με χρήση Canvas API στο Android
  • Κύκλος απόδοσης αποτελείται από τις φάσεις measure, layout και draw — η onDraw καλείται στη φάση draw
  • Canvas API περιλαμβάνει μεθόδους drawCircle, drawRect, drawPath, drawBitmap, drawText και άλλες
  • invalidate() ζητά επανασχεδίαση από το νήμα UI, postInvalidate() — από το νήμα παρασκηνίου
  • Μερική επανασχεδίαση μέσω invalidate(Rect) βελτιστοποιεί την απόδοση, ενημερώνοντας μόνο την dirty περιοχή
  • Η απόδοση της onDraw είναι κρίσιμη: η δημιουργία αντικειμένων εντός της μεθόδου προκαλεί GC και παράλειψη καρέ
  • Επιτάχυνση υλικού (από API 11) επιταχύνει αυτόματα τις λειτουργίες Canvas μέσω GPU

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

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

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