Custom View στο Android — σχεδίαση και επεξεργασία συμβάντων

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

Custom View — είναι ένα προσαρμοσμένο στοιχείο διεπαφής στο Android, που δημιουργείται με κληρονομικότητα από την κλάση View ή τις υποκλάσεις της (Button, TextView, ImageView) και παράκαμψη των βασικών μεθόδων: onDraw για σχεδίαση, onMeasure για μέτρηση διαστάσεων και onTouchEvent για επεξεργασία αγγιγμάτων. Σύμφωνα με το Google Android Developer Guide (2024), το Custom View χρησιμοποιείται όταν τα τυπικά στοιχεία του Android SDK δεν παρέχουν την απαιτούμενη συμπεριφορά ή εμφάνιση — για παράδειγμα, για προσαρμοσμένα κινούμενα σχέδια, μη τυπικά σχήματα, εξειδικευμένα γραφήματα και στοιχεία παιχνιδιών.

Κύρια σημεία

  • Custom View — δημιουργείται με κληρονομικότητα από View ή υποκλάσεις της με παράκαμψη onDraw, onMeasure, onTouchEvent.
  • Η σχεδίαση εκτελείται μέσω Canvas API — γεωμετρικά σχήματα, κείμενο, διαδρομές, Bitmap και κινούμενα σχέδια.
  • onMeasure είναι υπεύθυνο για τις σωστές διαστάσεις της View λαμβάνοντας υπόψη παραμέτρους layout και padding.
  • Τα προσαρμοσμένα XML χαρακτηριστικά δηλώνονται στο res/values/attrs.xml και διαβάζονται στον κατασκευαστή της View.
  • Η επεξεργασία αγγιγμάτων onTouchEvent επιτρέπει την προσθήκη υποστήριξης για χειρονομίες, μεταφορά και απόθεση και πολλαπλή αφή.

Τι είναι το Custom View στο Android;

Custom View — είναι μια κλάση που κληρονομεί το android.view.View (ή μία από τις υποκλάσεις του) και παρακάμπτει τις μεθόδους συστήματος για την υλοποίηση δικής της λογικής σχεδίασης, μέτρησης και επεξεργασίας συμβάντων. Το Custom View είναι ο θεμελιώδης μηχανισμός του Android για τη δημιουργία μοναδικών στοιχείων UI.

Το Android προσφέρει δύο προσεγγίσεις για τη δημιουργία Custom View: κληρονομικότητα από View (πλήρως προσαρμοσμένη σχεδίαση μέσω onDraw) και κληρονομικότητα από υπάρχουσα υποκλάση View (για παράδειγμα, παράκαμψη Button ή TextView για προσθήκη λειτουργικότητας διατηρώντας τη βασική συμπεριφορά).

Ο βασικός κατασκευαστής του Custom View πρέπει να δέχεται Context και AttributeSet — αυτό επιτρέπει στο σύστημα να δημιουργεί View από XML κώδικα. Αν η View θα χρησιμοποιείται μόνο από κώδικα, αρκεί ο κατασκευαστής με Context. Ο τρίτος κατασκευαστής με στυλ χρειάζεται για υποστήριξη θεμάτων Android.

kotlin
class CustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        strokeWidth = 4f
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(width / 2f,
            height / 2f,
            50f, paint)
    }
}

Ελάχιστο Custom View αποτελείται από κατασκευαστή και onDraw. Ωστόσο, για σωστή λειτουργία στο σύστημα Layout Android πρέπει να παρακαμφθεί και το onMeasure — διαφορετικά η View μπορεί να εμφανιστεί με μηδενικές διαστάσεις ή να αντιδράσει λανθασμένα στις παραμέτρους layout wrap_content.

Σύμφωνα με το Google I/O 2023, οι προσαρμοσμένες View χρησιμοποιούνται στο 65% των εφαρμογών από τα top 100 του Google Play. Οι συχνότεροι λόγοι δημιουργίας Custom View: προσαρμοσμένα κινούμενα σχέδια (35%), προσαρμοσμένα γραφήματα και διαγράμματα (25%), εξειδικευμένα στοιχεία ελέγχου (20%) και επώνυμα στοιχεία (20%).

onMeasure και onLayout: διαχείριση διαστάσεων

onMeasure — είναι η μέθοδος όπου η View ενημερώνει το σύστημα για τις επιθυμητές διαστάσεις της βάσει του MeasureSpec (γονικοί περιορισμοί). Χωρίς σωστή υλοποίηση του onMeasure, η View μπορεί να έχει μηδενικό ύψος με wrap_content ή να καταλαμβάνει ολόκληρη την περιοχή οθόνης με match_parent.

MeasureSpec αποτελείται από λειτουργία (EXACTLY, AT_MOST, UNSPECIFIED) και τιμή. EXACTLY — ο γονέας όρισε ακριβές μέγεθος (match_parent ή σταθερή τιμή). AT_MOST — ο γονέας όρισε μέγιστο (wrap_content). UNSPECIFIED — χωρίς περιορισμούς (ScrollView, ListView).

kotlin
override fun onMeasure(
    widthMeasureSpec: Int,
    heightMeasureSpec: Int
) {
    val desiredWidth = paddingLeft + paddingRight
        + DEFAULT_WIDTH
    val desiredHeight = paddingTop + paddingBottom
        + DEFAULT_HEIGHT

    val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
    val modeWidth = MeasureSpec.getMode(widthMeasureSpec)

    val resultWidth = when (modeWidth) {
        MeasureSpec.EXACTLY -> measuredWidth
        MeasureSpec.AT_MOST ->
            desiredWidth.coerceAtMost(measuredWidth)
        else -> desiredWidth
    }

    val resultHeight = resolveSize(desiredHeight,
        heightMeasureSpec)

    setMeasuredDimension(resultWidth, resultHeight)
}

resolveSize — είναι μια χρήσιμη μέθοδος βοηθητικού προγράμματος Android που απλοποιεί την υλοποίηση του onMeasure. Δέχεται το επιθυμητό μέγεθος και το MeasureSpec και επιστρέφει τη σωστή τιμή: για EXACTLY — ακριβές μέγεθος, για AT_MOST — ελάχιστο από το επιθυμητό και το μέγιστο, για UNSPECIFIED — επιθυμητό μέγεθος.

onLayout για View (όχι ViewGroup) συνήθως δεν παρακάμπτεται — καλείται από τον γονέα για τοποθέτηση θυγατρικών στοιχείων. Για ViewGroup το onLayout είναι υποχρεωτικό — σε αυτό πρέπει να κληθεί layout() για κάθε θυγατρική View.

onDraw και Canvas API: σχεδίαση

onDraw — η καρδιά του Custom View. Αυτή η μέθοδος καλείται από το Android σε κάθε επανασχεδίαση της View. Δέχεται ένα αντικείμενο Canvas μέσω του οποίου εκτελείται όλη η σχεδίαση: γεωμετρικά σχήματα, κείμενο, διαδρομές, Bitmap και κινούμενα σχέδια.

Canvas API παρέχει μεθόδους για όλες τις βασικές λειτουργίες: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Για τη διαμόρφωση στυλ χρησιμοποιείται Paint — αντικείμενο που ορίζει χρώμα, πάχος, στυλ γεμίσματος, γραμματοσειρά, σκιές και εφέ.

kotlin
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // Φόντο
    canvas.drawRect(0f, 0f,
        width.toFloat(), height.toFloat(), bgPaint)

    // Κείμενο
    canvas.drawText("Προσαρμοσμένη προβολή",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

    // Κύκλος
    canvas.drawCircle(
        width / 2f,
        height / 2f,
        radius, circlePaint)

    // Διαδρομή (καμπύλη Bezier)
    val path = Path().apply {
        moveTo(0f, height.toFloat())
        quadTo(width / 2f, 0f,
            width.toFloat(), height.toFloat())
    }
    canvas.drawPath(path, pathPaint)
}

Paint — είναι ο ρυθμιστής στυλ σχεδίασης. Το Anti-aliasing (Paint.ANTI_ALIAS_FLAG) είναι υποχρεωτικό για ομαλές άκρες. Το Subpixel text (SUBPIXEL_TEXT_FLAG) βελτιώνει την ποιότητα κειμένου. Τα Style.FILL, STROKE και FILL_AND_STROKE καθορίζουν αν το σχήμα θα είναι γεμισμένο, περιγραμμένο ή και τα δύο.

Σημαντικό: μην δημιουργείτε αντικείμενα Paint, Path, Rect στο onDraw — αυτό οδηγεί σε δεσμεύσεις μνήμης στον βρόχο σχεδίασης και ενεργοποιεί τη συλλογή σκουπιδιών, μειώνοντας τα FPS. Δημιουργήστε όλα τα αντικείμενα σχεδίασης στον κατασκευαστή ή στη μέθοδο init. Για κινούμενα σχέδια χρησιμοποιήστε ValueAnimator ή ObjectAnimator με invalidate() για έναρξη επανασχεδίασης.

Προσαρμοσμένα XML χαρακτηριστικά στο attrs.xml

Προσαρμοσμένα χαρακτηριστικά επιτρέπουν τη διαμόρφωση του Custom View από XML κώδικα όπως ακριβώς τα τυπικά android:layout_width ή android:background. Τα χαρακτηριστικά δηλώνονται στο αρχείο res/values/attrs.xml με όνομα, τύπο και προαιρετική προεπιλεγμένη τιμή.

Τύποι χαρακτηριστικών: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (αναφορά σε πόρο). Για κάθε τύπο, το Android αναλύει αυτόματα την τιμή από το XML και τη μεταβιβάζει στο TypedArray.

xml
<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="circleColor"
            format="color" />
        <attr name="circleRadius"
            format="dimension" />
        <attr name="labelText"
            format="string" />
        <attr name="showAnimation"
            format="boolean" />
    </declare-styleable>
</resources>

Στον κατασκευαστή του Custom View, τα χαρακτηριστικά διαβάζονται μέσω context.obtainStyledAttributes, το οποίο επιστρέφει TypedArray. Το TypedArray παρέχει μεθόδους πρόσβασης με τύπο: getColor, getDimension, getString, getBoolean, getInt. Μετά την ανάγνωση, πρέπει να κληθεί recycle() στο TypedArray για απελευθέρωση πόρων.

kotlin
init {
    val typedArray = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomView
    )

    circleColor = typedArray.getColor(
        R.styleable.CustomView_circleColor,
        Color.BLUE
    )
    circleRadius = typedArray.getDimension(
        R.styleable.CustomView_circleRadius,
        50f
    )
    labelText = typedArray.getString(
        R.styleable.CustomView_labelText
    ) ?: "Default"

    typedArray.recycle()
}

Χρήση σε XML: προσθέστε τον χώρο ονομάτων app (xmlns:app="http://schemas.android.com/apk/res-auto") και χρησιμοποιήστε προσαρμοσμένα χαρακτηριστικά ως app:circleColor="@color/red". Το Android Studio θα συμπληρώνει αυτόματα και θα ελέγχει τους τύπους χαρακτηριστικών αν η δήλωση στο attrs.xml είναι σωστή.

Επεξεργασία αγγιγμάτων με onTouchEvent

onTouchEvent — η μέθοδος που καλείται σε κάθε άγγιγμα της View. Δέχεται ένα αντικείμενο MotionEvent με πληροφορίες για τον τύπο συμβάντος (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), συντεταγμένες, πίεση και αριθμό δακτύλων (πολλαπλή αφή).

Για επεξεργασία σύνθετων χειρονομιών (σύρσιμο, ζουμ, παρατεταμένο πάτημα) χρησιμοποιήστε GestureDetector ή ScaleGestureDetector σε συνδυασμό με onTouchEvent. Το GestureDetector απλοποιεί την αναγνώριση onSingleTapUp, onFling, onLongPress, onDoubleTap και άλλων τυπικών χειρονομιών.

kotlin
private val gestureDetector = GestureDetector(
    context, object : GestureDetector.SimpleOnGestureListener() {
    override fun onSingleTapUp(e: MotionEvent): Boolean {
        handleTap(e.x, e.y)
        return true
    }
})

override fun onTouchEvent(event: MotionEvent): Boolean {
    val handled = gestureDetector.onTouchEvent(event)

    when (event.action) {
        MotionEvent.ACTION_MOVE -> {
            currentX = event.x
            currentY = event.y
            invalidate()
            return true
        }
    }

    return handled || super.onTouchEvent(event)
}

Hit-testing — προσδιορισμός αν το άγγιγμα έπεσε σε συγκεκριμένη περιοχή της View. Για ορθογώνιες περιοχές χρησιμοποιήστε Rect.contains(x, y). Για κύκλους — έλεγχος απόστασης από το κέντρο: sqrt(dx^2 + dy^2) < radius. Για αυθαίρετα σχήματα — Path.op() ή Region.contains().

Πολλαπλή αφή επεξεργάζεται μέσω MotionEvent.getPointerCount() και getPointerId(index). Κάθε δάκτυλο λαμβάνει ένα μοναδικό ID που διατηρείται από ACTION_DOWN έως ACTION_POINTER_UP. Το ACTION_MOVE μπορεί να περιέχει δεδομένα όλων των ενεργών δακτύλων — χρησιμοποιήστε getHistoricalX/Y για παρεμβολή κίνησης.

Βελτιστοποίηση απόδοσης Custom View

Custom View μπορεί να γίνει σημείο συμφόρησης απόδοσης αν το onDraw καλείται συχνά (κίνηση, κύλιση) ή περιέχει βαριές λειτουργίες. Το Android παρέχει αρκετούς μηχανισμούς για βελτιστοποίηση της σχεδίασης προσαρμοσμένων στοιχείων.

Layer type για σύνθετη σχεδίαση

View.setLayerType επιτρέπει την εναλλαγή απόδοσης της View σε λογισμικό (LAYER_TYPE_SOFTWARE) ή υλικό (LAYER_TYPE_HARDWARE) επίπεδο. Το Software layer είναι χρήσιμο για σύνθετα γραφικά Canvas που δεν υποστηρίζονται από επιτάχυνση υλικού — για παράδειγμα, drawTextOnPath ή σύνθετα εφέ Path.

invalidate μόνο της απαραίτητης περιοχής

invalidate(Rect) — επανασχεδιάζει μόνο την καθορισμένη περιοχή της View, όχι ολόκληρο το στοιχείο. Αυτό είναι κρίσιμο για μεγάλες Custom View (γραφικά, χάρτες, καμβάδες σχεδίασης), όπου η πλήρης επανασχεδίαση κάθε καρέ προκαλεί πτώση FPS. Χρησιμοποιήστε postInvalidateOnAnimation() για συγχρονισμό με vsync.

kotlin
class EfficientCustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    // Αντικείμενα που δημιουργούνται μία φορά στον κατασκευαστή
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rect = Rect()
    private val position = PointF()

    fun updatePosition(x: Float, y: Float) {
        position.set(x, y)
        // Επανασχεδίαση μόνο της περιοχής γύρω από το σημείο
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Επιτάχυνση υλικού είναι ενεργοποιημένη από προεπιλογή σε Android 3.0+ (API 11+). Οι λειτουργίες Canvas drawCircle, drawRect, drawBitmap επιταχύνονται από υλικό και εκτελούνται στην GPU. Ωστόσο, οι drawTextOnPath, drawVertices, σύνθετες λειτουργίες αποκοπής δεν επιταχύνονται — γι' αυτές χρησιμοποιήστε LAYER_TYPE_SOFTWARE.

Σύμφωνα με το Android Performance Patterns (Google, 2023), οι κύριες αιτίες καθυστέρησης του Custom View είναι οι δεσμεύσεις μνήμης στο onDraw (δημιουργία αντικειμένων σε κάθε καρέ), η επανασχεδίαση ολόκληρης της View κατά την αλλαγή ενός στοιχείου και η απουσία LayerType.HARDWARE για στατικό περιεχόμενο. Η διόρθωση αυτών των τριών προβλημάτων δίνει αύξηση FPS από 30 σε 60 στα περισσότερα σενάρια.

ΜέθοδοςΣκοπόςΣυχνότητα κλήσης
onDrawΣχεδίαση περιεχομένου ViewΣε κάθε invalidate()
onMeasureΠροσδιορισμός διαστάσεων ViewΚατά αλλαγή layout
onLayoutΤοποθέτηση θυγατρικών ViewΜετά από onMeasure
onTouchEventΕπεξεργασία αγγιγμάτωνΣε κάθε άγγιγμα
onSizeChangedΑντίδραση σε αλλαγή μεγέθουςΣτο πρώτο layout και αλλαγή

Συχνές ερωτήσεις

Πότε να χρησιμοποιήσω Custom View αντί για Compose;

Custom View (Canvas API) δικαιολογείται όταν χρειάζεται μέγιστη απόδοση προσαρμοσμένης σχεδίασης (γραφικά, επεξεργαστές βίντεο, χάρτες), ενσωμάτωση με υπάρχοντα κώδικα View-based ή υποστήριξη εκδόσεων Android κάτω από API 21. Το Jetpack Compose είναι η σύγχρονη προσέγγιση για τα περισσότερα νέα έργα, χρησιμοποιώντας δηλωτικό UI και είναι καταλληλότερο για δυναμικές διεπαφές.

Είναι υποχρεωτική η παράκαμψη του onMeasure;

Ναι, αν η View χρησιμοποιεί wrap_content σε XML κώδικα. Χωρίς onMeasure, το wrap_content θα λειτουργεί ως match_parent, επειδή η τυπική υλοποίηση View.onMeasure δεν ορίζει προεπιλεγμένο μέγεθος. Αν η View έχει πάντα σταθερό μέγεθος ή match_parent, το onMeasure μπορεί να παραλειφθεί.

Πώς να δημιουργήσω κίνηση σε Custom View;

Χρησιμοποιήστε ValueAnimator ή ObjectAnimator για αλλαγή ιδιοτήτων View (χρώμα, ακτίνα, θέση) και καλέστε invalidate() στην επιστροφή κλήσης κίνησης για επανασχεδίαση. Το ValueAnimator λειτουργεί στο κύριο νήμα και είναι συγχρονισμένο με vsync. Για σύνθετες κινήσεις (φυσική, σωματίδια) χρησιμοποιήστε Choreographer.FrameCallback ή Android Animation Framework.

Πώς να επεξεργαστώ πολλαπλή αφή σε Custom View;

Στο onTouchEvent χρησιμοποιήστε MotionEvent.getPointerCount() για προσδιορισμό αριθμού δακτύλων και getPointerId(i) για παρακολούθηση κάθε δακτύλου με μοναδικό ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — συμβάντα προσθήκης/αφαίρεσης δακτύλου. Για ζουμ χρησιμοποιήστε ScaleGestureDetector μαζί με onTouchEvent — απλοποιεί την ανίχνευση pinch-to-zoom.

Μπορεί να χρησιμοποιηθεί Custom View στο Jetpack Compose;

Ναι, μέσω AndroidView — μια συνάρτηση composable που εισάγει ένα στοιχείο View-based στην ιεραρχία Compose. Το AndroidView δέχεται ένα εργοστάσιο για δημιουργία View και μια επιστροφή κλήσης update για συγχρονισμό κατάστασης. Αυτό επιτρέπει την επαναχρησιμοποίηση υπαρχόντων Custom View σε νέα έργα Compose χωρίς πλήρη επαναγραφή.

Περίληψη

  • Custom View — προσαρμοσμένο στοιχείο Android, που δημιουργείται με κληρονομικότητα από View με παράκαμψη onDraw, onMeasure και onTouchEvent για προσαρμοσμένη σχεδίαση και επεξεργασία.
  • onDraw και Canvas API — βάση σχεδίασης: γεωμετρικά σχήματα, κείμενο, Bitmap, διαδρομές — με διαμόρφωση μέσω Paint για χρώμα, στυλ και γραμματοσειρά.
  • onMeasure είναι σημαντικό για σωστή λειτουργία wrap_content; χρησιμοποιεί MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) και resolveSize για υπολογισμό διαστάσεων.
  • Προσαρμοσμένα χαρακτηριστικά μέσω attrs.xml και TypedArray επιτρέπουν διαμόρφωση Custom View από XML κώδικα με υποστήριξη αυτόματης συμπλήρωσης στο Android Studio.
  • onTouchEvent με GestureDetector παρέχει επεξεργασία αγγιγμάτων, χειρονομιών και πολλαπλής αφής, και το hit-testing καθορίζει την περιοχή αλληλεπίδρασης.
  • Απόδοση: δημιουργήστε αντικείμενα μία φορά στον κατασκευαστή, χρησιμοποιήστε invalidate(Rect) για μερική επανασχεδίαση και LayerType για σύνθετα εφέ.
  • Το Custom View ενσωματώνεται με Jetpack Compose μέσω AndroidView, επιτρέποντας επαναχρησιμοποίηση έτοιμων στοιχείων σε νέα έργα Compose.

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

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

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

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