TextMeasurer — je API z Jetpack Compose určené k měření rozměrů textu před jeho skutečným vykreslením na obrazovce. Na rozdíl od klasického přístupu přes Paint.measureText v Android View, TextMeasurer poskytuje Compose-nativní způsob, jak získat šířku, výšku a metriky řádků s ohledem na styl a omezení. Podle Android Developers Documentation (2025) je TextMeasurer aktivně používán ve vlastních Layout komponentách, animovaných popiscích a adaptivních UI prvcích, kde velikost textu ovlivňuje umístění ostatních prvků.
Hlavní body
TextMeasurer — je funkční rozhraní z balíčku androidx.compose.ui.text, které umožňuje měřit text v Compose bez přímého vykreslení na obrazovce. Je součástí textového enginu Compose a pracuje na úrovni Paragraph API, které používá Skia pro vykreslování.
Hlavním účelem TextMeasurer je předběžné měření. V Compose se velikost textu obvykle určuje poté, co je Composable již umístěn v Layout. Existují ale scénáře, kde potřebujete znát šířku textu před kompozicí: například pro rozhodnutí, zda se řádek vejde do kontejneru, nebo pro výpočet velikosti tlačítka na základě délky textu.
TextMeasurer řeší tento problém poskytováním metody measure(), která přijímá TextMeasureRequest (text, styl, omezení) a vrací TextLayoutResult (metriky, řádky, znaky). To umožňuje vývojáři získat všechna data o textu synchronně, bez zpoždění jednoho snímku.
Použijte TextMeasurer, když velikost textu ovlivňuje velikost rodičovské komponenty, při vytváření animovaných textových přechodů nebo při implementaci vlastních Layout, kde text sousedí s jinými prvky.
API TextMeasurer se skládá ze dvou klíčových entit: rememberTextMeasurer() (vytvoření instance) a measure() (spuštění měření). Instance je vytvořena jednou pro celý životní cyklus komponenty a je znovu používána pro všechna měření.
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
text = "Hello, Compose!",
style = MaterialTheme.typography.bodyLarge,
constraints = Constraints(maxWidth = 200, maxHeight = 50)
)
// šířka výsledku, výška výsledku, počet řádků
Parametr constraints určuje maximální šířku a výšku, do které se text musí vejít. Pokud se text nevejde na šířku, je přesunut na další řádek. Výsledek měření obsahuje TextLayoutResult, ve kterém můžete získat počet řádků, pozici každého znaku, šířku každého řádku a celkovou velikost textu.
TextMeasurer pracuje synchronně v UI vlákně a nezpůsobuje recompozici. Časté měření textu s různými styly však může vytvářet zátěž — proto je instance vytvářena přes remember a znovu používána.
Nejjednodušší scénář — změřit šířku jednořádkového textu pro určení velikosti rodičovského kontejneru. Například pro tlačítko, jehož šířka závisí na délce popisku.
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)
}
V tomto příkladu remember(label) zaručuje, že se měření restartuje pouze při změně textu. Constraints jsou nastaveny s velkým maxWidth (1000), aby se zabránilo zalomení řádku — skutečné omezení bude rovno šířce textu. Šířka Boxu je nastavena na naměřenou šířku textu, což dává efekt „tlačítka podle velikosti textu".
Poznámka: šířku z výsledku je třeba převést na dp pomocí .dp, protože TextMeasurer vrací rozměry v pixelech, zatímco Modifier.width očekává Dp. Pro přesnost použijte density a LocalDensity.current.
Pro víceřádkový text umožňuje TextMeasurer určit nejen celkovou výšku, ale také počet řádků a pozici každého znaku v odstavci. To je nezbytné při vytváření vlastních textových polí, chatů nebo editorů.
Hlavní vlastnosti TextLayoutResult pro víceřádkový text: lineCount (počet řádků), getLineTop(index) (Y-souřadnice začátku řádku), getLineBottom(index) (Y-souřadnice konce), getLineWidth(index) (šířka řádku). Jejich kombinací můžete přesně umístit prvky rozhraní vzhledem k řádkům textu.
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
Metoda getLineWidth(lineIndex) vrací šířku zadaného řádku v pixelech. Může být menší než maxWidth, pokud řádek nedosahuje okraje kontejneru. Se znalostí šířky každého řádku můžete ručně implementovat efekt zarovnání do bloku nebo umístit inline prvky vedle krátkých řádků.
Pro získání pozice konkrétního znaku použijte getBoundingBox(offset), který vrací Rect se souřadnicemi left, top, right, bottom. To je užitečné pro pozicování kurzoru ve vlastních textových polích nebo pro zvýraznění rozsahu textu animací.
Nejvýkonnější scénář použití TextMeasurer — vlastní Layout komponenty, kde text sousedí s jinými prvky a jejich velikosti jsou vzájemně závislé. Například widget „Ikona + text", kde ikona má být centrována vzhledem k prvnímu řádku textu, nikoli k celému bloku.
Ve standardním Row je ikona centrována na výšku celého Row, což vypadá nepřirozeně, když text zabírá 3 řádky a ikona jeden. S TextMeasurer můžete změřit první řádek a zarovnat ikonu přesně vzhledem k němu.
val textMeasurer = rememberTextMeasurer()
Layout(
content = {
Icon(imageVector = Icons.Default.Star, contentDescription = null)
Text(text = "Víceřádkový text zde")
},
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)
}
}
)
V tomto příkladu měření prvního řádku přes TextMeasurer určuje pozici ikony. Bez TextMeasurer by ikona byla centrována na celou výšku víceřádkového textu, což je vizuálně nesprávné. Tento vzor je široce používán v chatech, seznamech kontaktů a informačních kartách.
TextMeasurer — je synchronní API pracující v UI vlákně. Při nadměrném používání může způsobovat ztrátu snímků, zejména při měření velkých objemů textu na slabších zařízeních.
První pravidlo — ukládejte výsledek do mezipaměti. Vždy obalujte volání measure() do remember se závislostmi na textu a stylu. Nikdy nevolejte measure() uvnitř CompositionLocal nebo na horkých cestách recompozice.
Druhé pravidlo — omezujte maxWidth a maxHeight. Pokud nastavíte maxWidth = Constraints.Infinity, TextMeasurer nebude moci zalomit řádky a text vyjede z obrazovky. Vždy předejte realistická omezení, zejména pro víceřádkový text.
Třetí — vyhýbejte se opakovanému měření se stejnými parametry. Pokud se text a styl nezměnily, výsledek measure() bude stejný. Zkombinujte měření s Deferred nebo LaunchedEffect pro asynchronní scénáře.
Často kladené otázky
TextMeasurer měří text před vykreslením, zatímco onSizeChanged až po něm. TextMeasurer je potřeba, když velikost textu ovlivňuje layout rodiče; onSizeChanged je vhodný pro reaktivní logiku po kompozici.
Ano, TextMeasurer.measure() přijímá AnnotatedString, který zahrnuje SpannableString s inline styly, odkazy a barvami. Všechny span jsou při měření zohledněny.
TextMeasurer pracuje na úrovni Skia vykreslování, kde jsou všechny rozměry v pixelech. Převádějte přes LocalDensity.current: density.run { width.toDp() }.
TextMeasurer používá FontFamily.Default do načtení vlastního písma. Pro přesné měření počkejte na načtení přes FontResource a použijte async/await.
Měření textu v každém prvku LazyColumn může snižovat FPS na slabších zařízeních. Optimalizace: ukládejte do mezipaměti výsledek přes remember a omezte velikost textu.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také