onMeasure() — είναι μια protected μέθοδος της κλάσης android.view.View, η οποία καλείται από το σύστημα Android για τον καθορισμό των διαστάσεων ενός View. Το σύστημα περνά δύο αντικείμενα MeasureSpec στη μέθοδο, καθένα από τα οποία περιέχει τη λειτουργία μέτρησης (EXACTLY, AT_MOST ή UNSPECIFIED) και το μέγεθος που προτείνεται από το γονικό δοχείο. Σύμφωνα με το Android Developers Documentation (2026), η παράκαμψη του onMeasure με σωστό χειρισμό του MeasureSpec είναι υποχρεωτικό βήμα για όλα τα προσαρμοσμένα View και ViewGroup που απαιτούν ακριβή έλεγχο των διαστάσεων.
Κύρια σημεία
onMeasure(int widthMeasureSpec, int heightMeasureSpec) — είναι η μέθοδος της κλάσης View που καλείται από το σύστημα Android για να καθορίσει το πλάτος και το ύψος της προβολής. Ο προγραμματιστής παρακάμπτει αυτή τη μέθοδο για να υποδείξει τι μέγεθος πρέπει να έχει το View με βάση τους περιορισμούς που μεταβιβάζονται στο MeasureSpec. Χωρίς σωστή παράκαμψη του onMeasure, το προσαρμοσμένο View μπορεί να εμφανίζεται λανθασμένα ή να μην εμφανίζεται καθόλου.
Το σύστημα καλεί το onMeasure στη φάση measure του κύκλου ζωής του View, η οποία προηγείται των φάσεων layout (onLayout) και draw (onDraw). Εάν το View δεν παρακάμπτει το onMeasure, χρησιμοποιείται η υλοποίηση από την υπερκλάση, η οποία ορίζει προεπιλεγμένες διαστάσεις βάσει του background drawable ή των layout_params. Η κλήση super.onMeasure(widthMeasureSpec, heightMeasureSpec) λειτουργεί μόνο για τυπικές υποκλάσεις View, όπως TextView ή ImageView.
Βασική απαίτηση για το onMeasure — η κλήση setMeasuredDimension(int, int) πρέπει υποχρεωτικά να υπάρχει στο τέλος της μεθόδου. Εάν αυτή η κλήση λείπει, το σύστημα δημιουργεί την εξαίρεση IllegalStateException με μήνυμα ότι το View δεν καθόρισε μετρημένες διαστάσεις. Οι τελικές διαστάσεις γίνονται διαθέσιμες μέσω των getter getMeasuredWidth() και getMeasuredHeight() μετά την ολοκλήρωση της φάσης measure.
MeasureSpec — είναι ένας 32-bit ακέραιος αριθμός, στον οποίο τα ανώτερα 2 bit κωδικοποιούν τη λειτουργία μέτρησης και τα κατώτερα 30 bit — το μέγεθος. Η λειτουργία καθορίζει πόσο ελεύθερο είναι το View στην επιλογή του μεγέθους του. Το Android προσφέρει τρεις λειτουργίες: EXACTLY, AT_MOST και UNSPECIFIED. Κάθε λειτουργία υπαγορεύει διαφορετική λογική επεξεργασίας στο onMeasure.
| Λειτουργία MeasureSpec | Τιμή | Συμπεριφορά |
|---|---|---|
| EXACTLY | Ο γονέας καθόρισε ακριβές μέγεθος | Το View πρέπει να χωρά ακριβώς στο μεταβιβαζόμενο μέγεθος, αν δεν θέλει να υπερβεί τα όρια |
| AT_MOST | Ο γονέας όρισε μέγιστο μέγεθος | Το View μπορεί να επιλέξει οποιοδήποτε μέγεθος από 0 έως το μεταβιβαζόμενο μέγιστο |
| UNSPECIFIED | Ο γονέας δεν επιβάλλει περιορισμούς | Το View μπορεί να επιλέξει οποιοδήποτε επιθυμητό μέγεθος χωρίς ανώτατους περιορισμούς |
Για την εξαγωγή της λειτουργίας και του μεγέθους από το MeasureSpec χρησιμοποιούνται στατικές μέθοδοι της κλάσης MeasureSpec: η MeasureSpec.getMode(int) επιστρέφει μία από τις τρεις λειτουργίες (EXACTLY, AT_MOST, UNSPECIFIED) και η MeasureSpec.getSize(int) επιστρέφει το αριθμητικό μέγεθος σε pixels. Για τη δημιουργία δικού σας MeasureSpec χρησιμοποιείται η MeasureSpec.makeMeasureSpec(int size, int mode). Αυτές οι τρεις μέθοδοι καλύπτουν όλα τα σενάρια εργασίας με διαστάσεις στο onMeasure.
Τυπικό μοτίβο επεξεργασίας MeasureSpec: εάν η λειτουργία είναι EXACTLY — χρησιμοποιήστε το μεταβιβαζόμενο μέγεθος ως τελικό· εάν είναι AT_MOST — επιλέξτε το ελάχιστο από το επιθυμητό μέγεθος (περιεχόμενο του View) και το μεταβιβαζόμενο μέγιστο· εάν είναι UNSPECIFIED — χρησιμοποιήστε το επιθυμητό μέγεθος του View χωρίς περιορισμούς. Αυτό το μοτίβο εγγυάται σωστή συμπεριφορά υπό οποιουσδήποτε περιορισμούς του γονέα.
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val desiredWidth = 200
val desiredHeight = 100
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
Για απλοποίηση της τυπικής λογικής, το Android παρέχει τη μέθοδο resolveSizeAndState, η οποία δέχεται το επιθυμητό μέγεθος, το MeasureSpec και επιστρέφει το τελικό μέγεθος με τη σωστή λειτουργία. Αυτή η μέθοδος υλοποιεί το μοτίβο που περιγράφηκε παραπάνω σε μία γραμμή κώδικα. Επίσης, διατίθεται η συνάρτηση resolveSize(int size, int measureSpec), η οποία επιστρέφει καθαρό μέγεθος χωρίς bit κατάστασης.
Android χρησιμοποιεί αλγόριθμο μέτρησης δύο περασμάτων (two-pass measure), ο οποίος εγγυάται ότι κάθε View στην ιεραρχία λαμβάνει σωστές διαστάσεις λαμβάνοντας υπόψη τους περιορισμούς του γονέα και τις προτιμήσεις των θυγατρικών στοιχείων. Στο πρώτο πέρασμα, ο γονέας μεταβιβάζει στα θυγατρικά View MeasureSpec με περιορισμούς και τα θυγατρικά View υπολογίζουν τις επιθυμητές διαστάσεις τους. Στο δεύτερο πέρασμα, ο γονέας λαμβάνει την τελική απόφαση για τις διαστάσεις.
Για ViewGroup η διαδικασία μέτρησης είναι πιο περίπλοκη: ο γονέας πρέπει πρώτα να μετρήσει όλα τα θυγατρικά του στοιχεία και στη συνέχεια να καθορίσει το δικό του μέγεθος βάσει των διαστάσεών τους. Η κλήση measureChildren(int widthMeasureSpec, int heightMeasureSpec) επαναλαμβάνεται σε όλα τα θυγατρικά View και καλεί για το καθένα measure(child, childWidthSpec, childHeightSpec). Αφού μετρηθούν όλα τα θυγατρικά στοιχεία, το ViewGroup καλεί setMeasuredDimension με τις δικές του διαστάσεις.
Σημαντική απόχρωση: η μέθοδος measure (public, final) δεν μπορεί να παρακαμφθεί — αντ' αυτής παρακάμπτεται το onMeasure. Αυτό εγγυάται ότι το σύστημα μπορεί να εκτελεί υπηρεσιακές ενέργειες πριν και μετά την κλήση του onMeasure, όπως έλεγχο αλλαγής διαστάσεων και υπολογισμό της βρώμικης περιοχής για επακόλουθη σχεδίαση. Εάν το View έχει σταθερές διαστάσεις, η παράκαμψη του onMeasure μπορεί να μην απαιτείται.
MeasureSpec περιλαμβάνει όχι μόνο το μέγεθος και τη λειτουργία, αλλά και bit κατάστασης (state bits), προσβάσιμα μέσω MeasureSpec.getMode(). Μετά την κλήση setMeasuredDimension, η κατάσταση γίνεται μέρος των μετρημένων διαστάσεων του View και μπορεί να ελεγχθεί μέσω getMeasuredState(). Αυτό χρησιμοποιείται στο ScrollView και άλλα κυλιόμενα δοχεία για σωστή μεταβίβαση περιορισμών στα παιδιά.
Ας εξετάσουμε ένα πρακτικό παράδειγμα δημιουργίας προσαρμοσμένου View με παράκαμψη onMeasure για τετράγωνη εμφάνιση. Η κλάση SquareView κληρονομεί το View και εγγυάται ότι το πλάτος και το ύψος είναι πάντα ίσα, ανεξάρτητα από τα μεταβιβαζόμενα MeasureSpec. Στο onMeasure καθορίζεται η μικρότερη πλευρά και ορίζεται το τετράγωνο μέγεθος.
class SquareView(context: Context)
: View(context) {
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val widthSize =
MeasureSpec.getSize(widthMeasureSpec)
val heightSize =
MeasureSpec.getSize(heightMeasureSpec)
val size = minOf(widthSize, heightSize)
setMeasuredDimension(size, size)
}
}
ViewGroup απαιτεί πιο περίπλοκη λογική onMeasure, καθώς πρώτα μετρώνται τα θυγατρικά στοιχεία και στη συνέχεια καθορίζεται το μέγεθος του ίδιου του ViewGroup. Το παράδειγμα CascadeLayout κατανέμει τα θυγατρικά στοιχεία σε καταρράκτη με διάκενο. Αφού μετρηθούν όλα τα θυγατρικά στοιχεία μέσω measureChildWithMargins, υπολογίζονται το συνολικό πλάτος και ύψος.
class CascadeLayout(context: Context)
: ViewGroup(context) {
private val cascadeOffset = 40
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
var maxWidth = 0
var totalHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec,
cascadeOffset * i,
heightMeasureSpec, 0)
maxWidth = maxOf(maxWidth,
child.measuredWidth +
cascadeOffset * i)
totalHeight += child.measuredHeight
}
setMeasuredDimension(
resolveSize(maxWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams = MarginLayoutParams(context, attrs)
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 left = l + cascadeOffset * i
child.layout(left, top,
left + child.measuredWidth,
top + child.measuredHeight)
top += child.measuredHeight
}
}
}
Παράλειψη κλήσης setMeasuredDimension — το πιο συνηθισμένο λάθος. Εάν ο προγραμματιστής παρέκαμψε το onMeasure αλλά δεν κάλεσε το setMeasuredDimension, η εφαρμογή θα καταρρεύσει με IllegalStateException. Αυτό συμβαίνει ιδιαίτερα συχνά όταν υπάρχουν υπό συνθήκη διακλαδώσεις στη μέθοδο και σε έναν από τους κλάδους η κλήση λείπει. Κάθε κλάδος κώδικα στο onMeasure πρέπει να τελειώνει με κλήση setMeasuredDimension.
Αγνόηση της λειτουργίας AT_MOST — το δεύτερο πιο συνηθισμένο λάθος. Εάν το View σε λειτουργία AT_MOST χρησιμοποιεί πάντα το μεταβιβαζόμενο μέγεθος αντί υπολογισμού βάσει περιεχομένου, το γονικό δοχείο δεν θα μπορεί να κατανείμει σωστά τον χώρο. Για παράδειγμα, το TextView σε AT_MOST πρέπει να υπολογίζει το πλάτος του κειμένου και να χρησιμοποιεί το ελάχιστο του επιθυμητού και του μεταβιβαζόμενου πλάτους. Η αγνόηση του AT_MOST έχει ως αποτέλεσμα το View να καταλαμβάνει όλο τον διαθέσιμο χώρο ακόμη και με μικρό περιεχόμενο.
Δημιουργία αντικειμένων εντός του onMeasure — ένα κλασικό λάθος απόδοσης. Δεδομένου ότι το onMeasure μπορεί να κληθεί πολλές φορές (σε κάθε αίτημα layout), η δημιουργία αντικειμένων (Paint, Rect, String) εντός αυτής της μεθόδου μολύνει τη μνήμη και προκαλεί συλλογή σκουπιδιών. Όλα τα αντικείμενα πρέπει να δημιουργούνται μία φορά στον κατασκευαστή του View και στο onMeasure πρέπει να εκτελείται μόνο η λογική υπολογισμού διαστάσεων. Ο ίδιος κανόνας ισχύει για τα onDraw και onLayout.
measureChildWithMargins — είναι μια protected μέθοδος του ViewGroup που μετρά ένα θυγατρικό View λαμβάνοντας υπόψη τα MarginLayoutParams του. Η μέθοδος δέχεται το MeasureSpec του γονέα και τις συσσωρευμένες μετατοπίσεις σε πλάτος και ύψος. Αυτόματα διορθώνει το MeasureSpec για το θυγατρικό View, αφαιρώντας τα padding του γονέα και το margin του παιδιού, και μεταβιβάζει το διορθωμένο MeasureSpec στο child.measure().
Για προηγμένη λογική μέτρησης, το ViewGroup μπορεί να παρακάμψει το measureChild(View child, int parentWidthSpec, int parentHeightSpec) ή να εργαστεί απευθείας με το MeasureSpec για κάθε θυγατρικό στοιχείο. Για παράδειγμα, το LinearLayout στο onMeasure επαναλαμβάνεται σε όλα τα θυγατρικά View, μετρά το καθένα λαμβάνοντας υπόψη το layout_weight του και κατανέμει τον υπόλοιπο χώρο αναλογικά. Αυτή η προσέγγιση επιτρέπει την υλοποίηση αυθαίρετων αλγορίθμων διάταξης.
Η προσωρινή αποθήκευση αποτελεσμάτων μέτρησης μέσω του μηχανισμού προσωρινής αποθήκευσης μέτρησης (measure cache) είναι διαθέσιμη μέσω της σημαίας setMeasureWithLargestChildEnabled σε συγκεκριμένα ViewGroup. Ωστόσο, στις περισσότερες περιπτώσεις το onMeasure καλείται ξανά σε κάθε αλλαγή διάταξης και η προσωρινή αποθήκευση δεν εφαρμόζεται. Σε προσαρμοσμένα ViewGroup, συνιστάται η ελαχιστοποίηση των υπολογισμών στο onMeasure και όχι η εξάρτηση από προσωρινή αποθήκευση.
Συχνές Ερωτήσεις
Ναι, εάν το προσαρμοσμένο View κληρονομεί απευθείας από την κλάση View. Εάν η κληρονομικότητα προέρχεται από TextView, ImageView ή Button με τις τυπικές τους διαστάσεις, το onMeasure μπορεί να μην παρακαμφθεί. Για ViewGroup, η παράκαμψη του onMeasure είναι πάντα απαραίτητη — διαφορετικά τα θυγατρικά στοιχεία δεν θα μετρηθούν σωστά.
Το σύστημα Android πετά IllegalStateException με το κείμενο “The View did not call setMeasuredDimension”. Αυτή η εξαίρεση προκύπτει στη μέθοδο measure() μετά την ολοκλήρωση του onMeasure, εάν οι τελικές διαστάσεις παρέμειναν μηδενικές. Η εξαίρεση καταρρέει την εφαρμογή εάν δεν αντιμετωπιστεί μέσω try-catch.
getMeasuredWidth() επιστρέφει το μέγεθος που ορίστηκε στο onMeasure (φάση μέτρησης). getWidth() επιστρέφει το πραγματικό μέγεθος που έλαβε το View στο onLayout μετά από όλες τις διορθώσεις τοποθέτησης. Για τα περισσότερα View, αυτές οι τιμές συμπίπτουν, αλλά σε προσαρμοσμένα ViewGroup μπορεί να διαφέρουν.
Όχι, το onMeasure προορίζεται αποκλειστικά για τον υπολογισμό διαστάσεων. Η αλλαγή κατάστασης, η έναρξη κινούμενων σχεδίων, η εργασία με δίκτυο ή η ενημέρωση δεδομένων σε αυτή τη μέθοδο παραβιάζει την αρχιτεκτονική Android και μπορεί να οδηγήσει σε αναδρομικές κλήσεις measure, καθώς η αλλαγή κατάστασης μπορεί να ενεργοποιήσει requestLayout.
ConstraintLayout διαχειρίζεται ανεξάρτητα τη μέτρηση των θυγατρικών στοιχείων βάσει δεδομένων περιορισμών. Εάν ένα προσαρμοσμένο View εντός ConstraintLayout παρακάμπτει το onMeasure, πρέπει να επεξεργάζεται σωστά το MeasureSpec που μεταβιβάζεται από το ConstraintLayout, διαφορετικά οι περιορισμοί ενδέχεται να μην λειτουργήσουν. Το ConstraintLayout χρησιμοποιεί αλγόριθμο δύο περασμάτων με δικό του WidgetContainer για υπολογισμό.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης