TextMeasurer — är ett API från Jetpack Compose avsett för att mäta textdimensioner innan den faktiska renderingen på skärmen. Till skillnad från den klassiska metoden via Paint.measureText i Android View, tillhandahåller TextMeasurer ett Compose-nativt sätt att få bredd, höjd och radmetrik med hänsyn till stil och begränsningar. Enligt Android Developers Documentation (2025) används TextMeasurer aktivt i anpassade Layout-komponenter, animerade bildtexter och adaptiva UI-element, där textstorleken påverkar positioneringen av andra element.
Huvudpunkter
TextMeasurer — är ett funktionellt gränssnitt från paketet androidx.compose.ui.text som gör det möjligt att mäta text i Compose utan direkt rendering på skärmen. Det är en del av Compose textmotor och arbetar på Paragraph API-nivå som använder Skia för rendering.
Huvudsyftet med TextMeasurer är förebyggande mätning. I Compose bestäms textstorleken vanligtvis efter att Composable redan har placerats i Layout. Men det finns scenarier där du behöver veta textbredden före kompositionen: till exempel för att avgöra om en rad får plats i containern, eller för att beräkna knappstorleken baserat på textlängden.
TextMeasurer löser detta problem genom att tillhandahålla metoden measure(), som tar emot TextMeasureRequest (text, stil, begränsningar) och returnerar TextLayoutResult (metrik, rader, tecken). Detta gör att utvecklaren kan få all data om texten synkront, utan en bildrutas fördröjning.
Använd TextMeasurer när textstorleken påverkar storleken på den överordnade komponenten, när du skapar animerade textövergångar eller när du implementerar anpassade Layout:er där text ligger intill andra element.
TextMeasurer API består av två nyckelenheter: rememberTextMeasurer() (skapande av instans) och measure() (start av mätning). Instansen skapas en gång för komponentens hela livscykel och återanvänds för alla mätningar.
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
text = "Hello, Compose!",
style = MaterialTheme.typography.bodyLarge,
constraints = Constraints(maxWidth = 200, maxHeight = 50)
)
// resultatets bredd, resultatets höjd, antal rader
Parametern constraints bestämmer den maximala bredden och höjden inom vilken texten måste få plats. Om texten inte får plats på bredden flyttas den till nästa rad. Mätresultatet innehåller TextLayoutResult, där du kan få antalet rader, positionen för varje tecken, bredden på varje rad och den totala textstorleken.
TextMeasurer arbetar synkront i UI-tråden och orsakar inte omkomposition. Dock kan frekvent mätning av text med olika stilar skapa belastning — därför skapas instansen via remember och återanvänds.
Det enklaste scenariot — mät bredden på enradig text för att bestämma storleken på den överordnade containern. Till exempel för en knapp vars bredd beror på etikettens längd.
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)
}
I detta exempel garanterar remember(label) att mätningen endast startas om när texten ändras. Constraints är inställda med en stor maxWidth (1000) för att förhindra radbrytning — den faktiska begränsningen kommer att vara lika med textens bredd. Boxens bredd ställs in på den uppmätta textbredden, vilket ger effekten "knapp efter textstorlek".
Observera: bredden från resultatet måste konverteras till dp via .dp, eftersom TextMeasurer returnerar mått i pixlar, medan Modifier.width förväntar sig Dp. För noggrannhet, använd density och LocalDensity.current.
För text med flera rader gör TextMeasurer det möjligt att bestämma inte bara den totala höjden, utan också antalet rader och positionen för varje tecken inom stycket. Detta är nödvändigt när du skapar anpassade textfält, chattar eller redigerare.
Huvudegenskaperna för TextLayoutResult för text med flera rader: lineCount (antal rader), getLineTop(index) (Y-koordinat för radens början), getLineBottom(index) (Y-koordinat för slutet), getLineWidth(index) (radbredd). Genom att kombinera dem kan du exakt positionera gränssnittselement i förhållande till textrader.
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
Metoden getLineWidth(lineIndex) returnerar bredden på den angivna raden i pixlar. Den kan vara mindre än maxWidth om raden inte når containergränsen. Genom att känna till bredden på varje rad kan du manuellt implementera en justeringseffekt eller placera inline-element bredvid korta rader.
För att få positionen för ett specifikt tecken, använd getBoundingBox(offset), som returnerar en Rect med koordinaterna left, top, right, bottom. Detta är användbart för att positionera markören i anpassade textfält eller för att markera ett textområde med animation.
Det mest kraftfulla användningsscenariot för TextMeasurer — anpassade Layout-komponenter, där text ligger intill andra element och deras storlekar är ömsesidigt beroende. Till exempel widgeten "Ikon + text", där ikonen ska centreras i förhållande till den första textraden, inte hela blocket.
I en standardrad centreras ikonen efter höjden på hela raden, vilket ser onaturligt ut när texten tar upp 3 rader och ikonen en. Med TextMeasurer kan du mäta den första raden och justera ikonen exakt i förhållande till den.
val textMeasurer = rememberTextMeasurer()
Layout(
content = {
Icon(imageVector = Icons.Default.Star, contentDescription = null)
Text(text = "Text med flera rader här")
},
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)
}
}
)
I detta exempel bestämmer mätningen av den första raden via TextMeasurer ikonens position. Utan TextMeasurer skulle ikonen centreras över hela höjden av texten med flera rader, vilket är visuellt felaktigt. Detta mönster används brett i chattar, kontaktlistor och informationskort.
TextMeasurer — är ett synkront API som arbetar i UI-tråden. Vid överdriven användning kan det orsaka bildrutefall, särskilt vid mätning av stora textvolymer på svagare enheter.
Första regeln — cachelagra resultatet. Linda alltid anropet till measure() i remember med beroenden av text och stil. Anropa aldrig measure() inuti CompositionLocal eller på heta omkompositionsvägar.
Andra regeln — begränsa maxWidth och maxHeight. Om du ställer in maxWidth = Constraints.Infinity kan TextMeasurer inte bryta rader och texten kommer att gå utanför skärmen. Skicka alltid realistiska begränsningar, särskilt för text med flera rader.
Tredje — undvik upprepad mätning med samma parametrar. Om texten och stilen inte har ändrats kommer resultatet av measure() att vara detsamma. Kombinera mätning med Deferred eller LaunchedEffect för asynkrona scenarier.
Vanliga frågor
TextMeasurer mäter text före rendering, medan onSizeChanged mäter efter. TextMeasurer behövs när textstorleken påverkar förälderns layout; onSizeChanged är lämplig för reaktiv logik efter komposition.
Ja, TextMeasurer.measure() accepterar AnnotatedString som inkluderar SpannableString med inline-stilar, länkar och färger. Alla spans beaktas vid mätning.
TextMeasurer arbetar på Skia-renderingsnivå, där alla mått är i pixlar. Konvertera via LocalDensity.current: density.run { width.toDp() }.
TextMeasurer använder FontFamily.Default tills det anpassade teckensnittet har laddats. För noggrann mätning, vänta på laddning via FontResource och använd async/await.
Mätning av text i varje element i LazyColumn kan minska FPS på svagare enheter. Optimering: cachelagra resultatet via remember och begränsa textstorleken.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också