TextMeasurer ist eine API von Jetpack Compose, die zum Messen von Textabmessungen entwickelt wurde, bevor er tatsächlich auf dem Bildschirm gerendert wird. Im Gegensatz zum klassischen Ansatz über Paint.measureText in Android View bietet TextMeasurer eine Compose-native Möglichkeit, Breite, Höhe und Linienmetriken unter Berücksichtigung von Stil und Einschränkungen zu erhalten. Laut Android Developers Documentation (2025) wird TextMeasurer aktiv in benutzerdefinierten Layout-Komponenten, animierten Beschriftungen und adaptiven UI-Elementen eingesetzt, wo die Textgröße die Positionierung anderer Elemente beeinflusst.
Wichtige Punkte
TextMeasurer ist ein funktionales Interface aus dem Paket androidx.compose.ui.text, das es ermöglicht, Text in Compose zu messen, ohne ihn tatsächlich auf dem Bildschirm darzustellen. Es ist Teil der Compose-Text-Engine und arbeitet auf der Ebene der Paragraph-API, die Skia zum Rendern verwendet.
Der Hauptzweck von TextMeasurer ist die Vormessung. In Compose wird die Textgröße normalerweise bestimmt, nachdem das Composable bereits im Layout platziert wurde. Es gibt jedoch Szenarien, in denen die Textbreite vor dem Layout bekannt sein muss – zum Beispiel um zu entscheiden, ob eine Zeichenfolge in einen Container passt, oder um die Größe einer Schaltfläche basierend auf der Textlänge zu berechnen.
TextMeasurer löst dies, indem es die measure()-Methode bereitstellt, die einen TextMeasureRequest (Text, Stil, Einschränkungen) entgegennimmt und TextLayoutResult (Metriken, Zeilen, Zeichen) zurückgibt. Dadurch kann der Entwickler alle Textdaten synchron und ohne eine Frame-Verzögerung abrufen.
Verwenden Sie TextMeasurer, wenn die Textgröße die Größe der Elternkomponente beeinflusst, beim Erstellen animierter Textübergänge oder bei der Implementierung benutzerdefinierter Layouts, bei denen Text mit anderen Elementen koexistiert.
Die TextMeasurer-API besteht aus zwei Schlüsselentitäten: rememberTextMeasurer() (Erstellen einer Instanz) und measure() (Durchführen der Messung). Die Instanz wird einmal für den gesamten Komponentenlebenszyklus erstellt und für alle Messungen wiederverwendet.
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
text = "Hello, Compose!",
style = MaterialTheme.typography.bodyLarge,
constraints = Constraints(maxWidth = 200, maxHeight = 50)
)
// result.size.width, result.size.height, result.lineCount
Der Parameter constraints definiert die maximale Breite und Höhe, innerhalb derer der Text passen soll. Wenn der Text nicht in die Breite passt, wird er in die nächste Zeile umgebrochen. Das Messergebnis enthält ein TextLayoutResult, das die Anzahl der Zeilen, die Position jedes Zeichens, die Breite jeder Zeile und die Gesamttextgröße bereitstellt.
TextMeasurer arbeitet synchron im UI-Thread und löst keine Neuzusammensetzung aus. Häufiges Messen von Text mit unterschiedlichen Stilen kann jedoch zu Überlast führen – daher wird die Instanz über remember erstellt und wiederverwendet.
Das einfachste Szenario ist das Messen der Breite von einzeiligem Text, um die Größe des Elterncontainers zu bestimmen. Zum Beispiel für eine Schaltfläche, deren Breite von der Länge der Beschriftung abhängt.
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)
}
In diesem Beispiel stellt remember(label) sicher, dass die Messung nur neu gestartet wird, wenn sich der Text ändert. Die Einschränkungen sind mit einem großen maxWidth (1000) gesetzt, um Zeilenumbrüche zu verhindern – die tatsächliche Einschränkung entspricht der Textbreite. Die Box-Breite wird auf die gemessene Textbreite gesetzt, was einen „Schaltfläche angepasst an den Text“-Effekt ergibt.
Hinweis: Die Breite aus dem Ergebnis muss mit .dp in dp umgewandelt werden, da TextMeasurer Dimensionen in Pixeln zurückgibt, während Modifier.width Dp erwartet. Verwenden Sie für die Genauigkeit density und LocalDensity.current.
Für mehrzeiligen Text ermöglicht TextMeasurer nicht nur die Bestimmung der Gesamthöhe, sondern auch der Anzahl der Zeilen und der Position jedes Zeichens innerhalb des Absatzes. Dies ist bei der Erstellung benutzerdefinierter Textfelder, Chats oder Editoren erforderlich.
Die wichtigsten TextLayoutResult-Eigenschaften für mehrzeiligen Text: lineCount (Anzahl der Zeilen), getLineTop(index) (Y-Koordinate des Zeilenanfangs), getLineBottom(index) (Y-Koordinate des Zeilenendes), getLineWidth(index) (Zeilenbreite). Durch Kombination dieser Werte können Sie Schnittstellenelemente präzise relativ zu Textzeilen positionieren.
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
Die Methode getLineWidth(lineIndex) gibt die Breite der angegebenen Zeile in Pixeln zurück. Sie kann kleiner als maxWidth sein, wenn die Zeile die Containergrenze nicht erreicht. Wenn Sie die Breite jeder Zeile kennen, können Sie manuell einen Blocksatz-Effekt implementieren oder Inline-Elemente neben kurzen Zeilen platzieren.
Um die Position eines bestimmten Zeichens zu erhalten, verwenden Sie getBoundingBox(offset), das ein Rect mit den Koordinaten left, top, right, bottom zurückgibt. Dies ist nützlich für die Cursorpositionierung in benutzerdefinierten Textfeldern oder zum Hervorheben eines Textbereichs mit Animation.
Die leistungsstärkste Anwendung von TextMeasurer sind benutzerdefinierte Layout-Komponenten, bei denen Text mit anderen Elementen koexistiert und ihre Größen voneinander abhängen. Zum Beispiel ein „Symbol + Text“-Widget, bei dem das Symbol relativ zur ersten Textzeile und nicht zum gesamten Block zentriert werden soll.
In einer standardmäßigen Row wird das Symbol über die gesamte Höhe der Row zentriert, was unnatürlich aussieht, wenn der Text 3 Zeilen einnimmt, das Symbol jedoch nur eine. Mit TextMeasurer können Sie die erste Zeile messen und das Symbol genau daran ausrichten.
val textMeasurer = rememberTextMeasurer()
Layout(
content = {
Icon(imageVector = Icons.Default.Star, contentDescription = null)
Text(text = "Multi-line text here")
},
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)
}
}
)
In diesem Beispiel bestimmt die Messung der ersten Zeile über TextMeasurer die Symbolposition. Ohne TextMeasurer würde das Symbol über die gesamte Höhe des mehrzeiligen Textes zentriert, was visuell falsch ist. Dieses Muster wird häufig in Chats, Kontaktlisten und Informationskarten verwendet.
TextMeasurer ist eine synchrone API, die im UI-Thread ausgeführt wird. Übermäßiger Gebrauch kann zu Frame-Einbrüchen führen, insbesondere beim Messen großer Textmengen auf schwachen Geräten.
Erste Regel – Ergebnis cachen. Wickeln Sie den measure()-Aufruf immer in remember mit Text- und Stilabhängigkeiten ein. Rufen Sie measure() niemals innerhalb von CompositionLocal oder in heißen Neuzusammensetzungspfaden auf.
Zweite Regel – maxWidth und maxHeight begrenzen. Wenn Sie maxWidth = Constraints.Infinity setzen, kann TextMeasurer keine Zeilen umbrechen und der Text verlässt den Bildschirm. Geben Sie immer realistische Einschränkungen an, insbesondere für mehrzeiligen Text.
Dritte Regel – Wiederholte Messung mit denselben Parametern vermeiden. Wenn sich Text und Stil nicht geändert haben, ist das Ergebnis von measure() dasselbe. Kombinieren Sie die Messung mit Deferred oder LaunchedEffect für asynchrone Szenarien.
Häufig gestellte Fragen
TextMeasurer misst Text vor dem Rendern, während onSizeChanged danach misst. TextMeasurer wird benötigt, wenn die Textgröße das Eltern-Layout beeinflusst; onSizeChanged eignet sich für reaktive Logik nach der Zusammensetzung.
Ja, TextMeasurer.measure() akzeptiert AnnotatedString, der SpannableString mit Inline-Stilen, Links und Farben enthält. Alle Spans werden bei der Messung berücksichtigt.
TextMeasurer arbeitet auf der Skia-Rendering-Ebene, wo alle Maße in Pixeln sind. Konvertieren Sie über LocalDensity.current: density.run { width.toDp() }.
TextMeasurer verwendet FontFamily.Default, bis eine benutzerdefinierte Schriftart geladen ist. Warten Sie für genaue Messungen auf das Laden der Schriftart über FontResource und verwenden Sie async/await.
Das Messen von Text in jedem LazyColumn-Element kann die FPS auf schwachen Geräten reduzieren. Optimierung: Ergebnis cachen über remember und Textgröße begrenzen.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch