TextMeasurer: τι είναι και πώς λειτουργεί η μέτρηση κειμένου στο Compose

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

TextMeasurer — είναι ένα API από το Jetpack Compose που προορίζεται για τη μέτρηση διαστάσεων κειμένου πριν από την πραγματική απόδοση στην οθόνη. Σε αντίθεση με την κλασική προσέγγιση μέσω Paint.measureText στο Android View, το TextMeasurer παρέχει έναν Compose-εγγενή τρόπο για να λάβετε πλάτος, ύψος και μετρήσεις γραμμών λαμβάνοντας υπόψη το στυλ και τους περιορισμούς. Σύμφωνα με το Android Developers Documentation (2025), το TextMeasurer χρησιμοποιείται ενεργά σε προσαρμοσμένα Layout στοιχεία, κινούμενες λεζάντες και προσαρμοστικά UI στοιχεία, όπου το μέγεθος κειμένου επηρεάζει την τοποθέτηση άλλων στοιχείων.

Κύρια σημεία

  • TextMeasurer — Compose-API για μέτρηση κειμένου πριν από την απόδοση.
  • Η μέθοδος measure() επιστρέφει TextLayoutResult με μετρήσεις γραμμών και χαρακτήρων.
  • Το API λαμβάνει υπόψη στυλ, γραμματοσειρά, μέγεθος και περιορισμούς του δοχείου σε pixel.
  • Το TextMeasurer υποστηρίζει μέτρηση μίας γραμμής και πολυγραμμικού κειμένου.
  • Το αποτέλεσμα μέτρησης χρησιμοποιείται για προσαρμοσμένη διάταξη και κίνηση κειμένου.

Τι είναι το TextMeasurer στο Jetpack Compose;

TextMeasurer — είναι μια λειτουργική διεπαφή από το πακέτο androidx.compose.ui.text που επιτρέπει τη μέτρηση κειμένου στο Compose χωρίς άμεση απόδοση στην οθόνη. Αποτελεί μέρος της μηχανής κειμένου του Compose και λειτουργεί σε επίπεδο Paragraph API που χρησιμοποιεί Skia για απόδοση.

Ο κύριος σκοπός του TextMeasurer είναι η προκαταρκτική μέτρηση. Στο Compose, το μέγεθος κειμένου συνήθως καθορίζεται αφού το Composable έχει ήδη τοποθετηθεί στο Layout. Αλλά υπάρχουν σενάρια όπου πρέπει να γνωρίζετε το πλάτος κειμένου πριν από τη σύνθεση: για παράδειγμα, για να αποφασίσετε αν μια γραμμή χωράει στο δοχείο, ή για να υπολογίσετε το μέγεθος ενός κουμπιού με βάση το μήκος κειμένου.

Το TextMeasurer λύνει αυτό το πρόβλημα παρέχοντας τη μέθοδο measure(), η οποία δέχεται TextMeasureRequest (κείμενο, στυλ, περιορισμούς) και επιστρέφει TextLayoutResult (μετρήσεις, γραμμές, χαρακτήρες). Αυτό επιτρέπει στον προγραμματιστή να λάβει όλα τα δεδομένα για το κείμενο σύγχρονα, χωρίς καθυστέρηση ενός καρέ.

Χρησιμοποιήστε το TextMeasurer όταν το μέγεθος κειμένου επηρεάζει το μέγεθος του γονικού στοιχείου, κατά τη δημιουργία κινούμενων μεταβάσεων κειμένου ή κατά την υλοποίηση προσαρμοσμένων Layout όπου το κείμενο γειτνιάζει με άλλα στοιχεία.

Πώς λειτουργεί το TextMeasurer

Το API TextMeasurer αποτελείται από δύο βασικές οντότητες: rememberTextMeasurer() (δημιουργία στιγμιότυπου) και measure() (έναρξη μέτρησης). Το στιγμιότυπο δημιουργείται μία φορά για ολόκληρο τον κύκλο ζωής του στοιχείου και επαναχρησιμοποιείται για όλες τις μετρήσεις.

kotlin
val textMeasurer = rememberTextMeasurer()

val result = textMeasurer.measure(
    text = "Hello, Compose!",
    style = MaterialTheme.typography.bodyLarge,
    constraints = Constraints(maxWidth = 200, maxHeight = 50)
)

// πλάτος αποτελέσματος, ύψος αποτελέσματος, αριθμός γραμμών

Η παράμετρος constraints καθορίζει το μέγιστο πλάτος και ύψος εντός των οποίων πρέπει να χωρέσει το κείμενο. Εάν το κείμενο δεν χωράει σε πλάτος, μεταφέρεται στην επόμενη γραμμή. Το αποτέλεσμα μέτρησης περιέχει TextLayoutResult, στο οποίο μπορείτε να λάβετε τον αριθμό γραμμών, τη θέση κάθε χαρακτήρα, το πλάτος κάθε γραμμής και το συνολικό μέγεθος κειμένου.

Το TextMeasurer λειτουργεί σύγχρονα στο νήμα UI και δεν προκαλεί ανασύνθεση. Ωστόσο, η συχνή μέτρηση κειμένου με διαφορετικά στυλ μπορεί να δημιουργήσει φόρτο — γι' αυτό το στιγμιότυπο δημιουργείται μέσω remember και επαναχρησιμοποιείται.

Μέτρηση μίας γραμμής κειμένου

Το απλούστερο σενάριο — μετρήστε το πλάτος μονογραμμικού κειμένου για να καθορίσετε το μέγεθος του γονικού δοχείου. Για παράδειγμα, για ένα κουμπί του οποίου το πλάτος εξαρτάται από το μήκος της ετικέτας.

kotlin
val textMeasurer = rememberTextMeasurer()
var label by remember { mutableStateOf("Submit") }

val textResult = remember(label) {
    textMeasurer.measure(
        text = label,
        style = MaterialTheme.typography.labelLarge,
        constraints = Constraints(maxWidth = 1000, maxHeight = 100)
    )
}

Box(
    modifier = Modifier
        .width(textResult.size.width.dp)
        .height(40.dp)
        .background(Color.Blue)
        .clickable { onClick() }
) {
    Text(text = label, style = MaterialTheme.typography.labelLarge)
}

Σε αυτό το παράδειγμα, το remember(label) εγγυάται ότι η μέτρηση επανεκκινείται μόνο όταν αλλάζει το κείμενο. Τα Constraints είναι ρυθμισμένα με μεγάλο maxWidth (1000) για να αποτραπεί η μεταφορά γραμμής — ο πραγματικός περιορισμός θα είναι ίσος με το πλάτος κειμένου. Το πλάτος του Box ορίζεται στο μετρημένο πλάτος κειμένου, δίνοντας το εφέ "κουμπί στο μέγεθος κειμένου".

Σημείωση: το πλάτος από το αποτέλεσμα πρέπει να μετατραπεί σε dp μέσω .dp, καθώς το TextMeasurer επιστρέφει μεγέθη σε pixel, ενώ το Modifier.width αναμένει Dp. Για ακρίβεια, χρησιμοποιήστε density και LocalDensity.current.

Μέτρηση πολυγραμμικού κειμένου

Για πολυγραμμικό κείμενο, το TextMeasurer επιτρέπει τον προσδιορισμό όχι μόνο του συνολικού ύψους, αλλά και του αριθμού γραμμών και της θέσης κάθε χαρακτήρα μέσα στην παράγραφο. Αυτό είναι απαραίτητο κατά τη δημιουργία προσαρμοσμένων πεδίων κειμένου, συνομιλιών ή επεξεργαστών.

Οι κύριες ιδιότητες του TextLayoutResult για πολυγραμμικό κείμενο: lineCount (αριθμός γραμμών), getLineTop(index) (συντεταγμένη Y έναρξης γραμμής), getLineBottom(index) (συντεταγμένη Y λήξης), getLineWidth(index) (πλάτος γραμμής). Συνδυάζοντάς τες, μπορείτε να τοποθετήσετε με ακρίβεια στοιχεία διεπαφής σε σχέση με τις γραμμές κειμένου.

kotlin
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
    text = "Long multiline text that needs to be measured before layout",
    style = MaterialTheme.typography.bodyMedium,
    constraints = Constraints(maxWidth = 150, maxHeight = 300)
)

val linesCount = result.lineCount
val firstLineWidth = result.getLineWidth(0)
val totalHeight = result.size.height

Η μέθοδος getLineWidth(lineIndex) επιστρέφει το πλάτος της καθορισμένης γραμμής σε pixel. Μπορεί να είναι μικρότερο από το maxWidth εάν η γραμμή δεν φτάνει στο όριο του δοχείου. Γνωρίζοντας το πλάτος κάθε γραμμής, μπορείτε να εφαρμόσετε χειροκίνητα το εφέ στοίχισης ή να τοποθετήσετε inline στοιχεία δίπλα σε σύντομες γραμμές.

Για να λάβετε τη θέση ενός συγκεκριμένου χαρακτήρα, χρησιμοποιήστε το getBoundingBox(offset), το οποίο επιστρέφει ένα Rect με συντεταγμένες left, top, right, bottom. Αυτό είναι χρήσιμο για την τοποθέτηση δρομέα σε προσαρμοσμένα πεδία κειμένου ή για την επισήμανση περιοχής κειμένου με κίνηση.

TextMeasurer σε προσαρμοσμένα στοιχεία

Το πιο ισχυρό σενάριο χρήσης του TextMeasurer — προσαρμοσμένα Layout στοιχεία, όπου το κείμενο γειτνιάζει με άλλα στοιχεία και τα μεγέθη τους είναι αλληλεξαρτώμενα. Για παράδειγμα, το widget "Εικονίδιο + κείμενο", όπου το εικονίδιο πρέπει να είναι κεντραρισμένο σε σχέση με την πρώτη γραμμή κειμένου, όχι ολόκληρο το μπλοκ.

Σε ένα τυπικό Row, το εικονίδιο κεντράρεται καθ' ύψος ολόκληρου του Row, το οποίο φαίνεται αφύσικο όταν το κείμενο καταλαμβάνει 3 γραμμές και το εικονίδιο μία. Με το TextMeasurer μπορείτε να μετρήσετε την πρώτη γραμμή και να στοιχίσετε το εικονίδιο ακριβώς σε σχέση με αυτήν.

kotlin
val textMeasurer = rememberTextMeasurer()

Layout(
    content = {
        Icon(imageVector = Icons.Default.Star, contentDescription = null)
        Text(text = "Πολυγραμμικό κείμενο εδώ")
    },
    measurePolicy = { measurables, constraints ->
        val textMeasurable = measurables[1]
        val textPlaceable = textMeasurable.measure(constraints)
        val iconPlaceable = measurables[0].measure(constraints)

        val firstLineHeight = textMeasurer.measure(
            text = "Sample",
            style = MaterialTheme.typography.bodyMedium,
            constraints = Constraints(maxWidth = 2000, maxHeight = 100)
        ).size.height

        layout(width = textPlaceable.width + iconPlaceable.width,
               height = maxOf(textPlaceable.height, iconPlaceable.height)) {
            iconPlaceable.placeRelative(0, (firstLineHeight - iconPlaceable.height) / 2)
            textPlaceable.placeRelative(iconPlaceable.width, 0)
        }
    }
)

Σε αυτό το παράδειγμα, η μέτρηση της πρώτης γραμμής μέσω TextMeasurer καθορίζει τη θέση του εικονιδίου. Χωρίς TextMeasurer, το εικονίδιο θα κεντραριζόταν σε ολόκληρο το ύψος του πολυγραμμικού κειμένου, το οποίο είναι οπτικά λάθος. Αυτό το μοτίβο χρησιμοποιείται ευρέως σε συνομιλίες, λίστες επαφών και κάρτες πληροφοριών.

Συμβουλές απόδοσης

TextMeasurer — είναι ένα σύγχρονο API που λειτουργεί στο νήμα UI. Σε υπερβολική χρήση, μπορεί να προκαλέσει πτώση καρέ, ειδικά κατά τη μέτρηση μεγάλου όγκου κειμένου σε αδύναμες συσκευές.

Πρώτος κανόνας — αποθηκεύστε στην κρυφή μνήμη το αποτέλεσμα. Πάντα τυλίγετε την κλήση measure() σε remember με εξαρτήσεις από το κείμενο και το στυλ. Ποτέ μην καλείτε το measure() μέσα σε CompositionLocal ή σε θερμές διαδρομές ανασύνθεσης.

Δεύτερος κανόνας — περιορίστε τα maxWidth και maxHeight. Εάν ορίσετε maxWidth = Constraints.Infinity, το TextMeasurer δεν θα μπορεί να μεταφέρει γραμμές και το κείμενο θα βγει έξω από την οθόνη. Πάντα δίνετε ρεαλιστικούς περιορισμούς, ειδικά για πολυγραμμικό κείμενο.

Τρίτος — αποφύγετε την επαναλαμβανόμενη μέτρηση με τις ίδιες παραμέτρους. Εάν το κείμενο και το στυλ δεν έχουν αλλάξει, το αποτέλεσμα του measure() θα είναι το ίδιο. Συνδυάστε τη μέτρηση με Deferred ή LaunchedEffect για ασύγχρονα σενάρια.

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

Σε τι διαφέρει το TextMeasurer από το onSizeChanged στο Compose;

Το TextMeasurer μετράει το κείμενο πριν από την απόδοση, ενώ το onSizeChanged μετά. Το TextMeasurer χρειάζεται όταν το μέγεθος κειμένου επηρεάζει τη διάταξη του γονέα· το onSizeChanged είναι κατάλληλο για αντιδραστική λογική μετά τη σύνθεση.

Μπορεί να μετρηθεί το SpannableString μέσω TextMeasurer;

Ναι, το TextMeasurer.measure() δέχεται AnnotatedString, το οποίο περιλαμβάνει SpannableString με inline στυλ, συνδέσμους και χρώματα. Όλα τα spans λαμβάνονται υπόψη κατά τη μέτρηση.

Γιατί το TextMeasurer επιστρέφει μέγεθος σε pixel, όχι σε dp;

Το TextMeasurer λειτουργεί σε επίπεδο απόδοσης Skia, όπου όλα τα μεγέθη είναι σε pixel. Μετατρέψτε μέσω LocalDensity.current: density.run { width.toDp() }.

Πώς να μετρήσω κείμενο πριν φορτωθεί η γραμματοσειρά;

Το TextMeasurer χρησιμοποιεί FontFamily.Default μέχρι τη φόρτωση της προσαρμοσμένης γραμματοσειράς. Για ακριβή μέτρηση, περιμένετε τη φόρτωση μέσω FontResource και χρησιμοποιήστε async/await.

Επηρεάζει το TextMeasurer την απόδοση λιστών LazyColumn;

Η μέτρηση κειμένου σε κάθε στοιχείο LazyColumn μπορεί να μειώσει το FPS σε αδύναμες συσκευές. Βελτιστοποίηση: αποθηκεύστε στην κρυφή μνήμη το αποτέλεσμα μέσω remember και περιορίστε το μέγεθος κειμένου.

Σύνοψη

  • TextMeasurer — Compose-API για μέτρηση κειμένου πριν από την απόδοση, επιστρέφει TextLayoutResult.
  • Η μέθοδος measure() δέχεται κείμενο, στυλ και Constraints σε pixel.
  • Το στιγμιότυπο δημιουργείται μέσω rememberTextMeasurer() και επαναχρησιμοποιείται.
  • Για μονογραμμικό κείμενο, το αποτέλεσμα χρησιμοποιείται για προσαρμοστικό πλάτος δοχείου.
  • Για πολυγραμμικό κείμενο — lineCount, getLineWidth() και getBoundingBox() δίνουν πλήρη εικόνα.
  • Σε προσαρμοσμένα Layout, το TextMeasurer επιτρέπει στοίχιση στοιχείων ως προς την πρώτη γραμμή κειμένου.
  • Αποθηκεύστε στην κρυφή μνήμη το αποτέλεσμα μέσω remember και αποφύγετε μέτρηση σε θερμές διαδρομές ανασύνθεσης.

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

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

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

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