TextMeasurer: co to je a jak funguje měření textu v Compose

Autor: IT Sectr Publikováno: 2026-07-25 Doba čtení: 8 min

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 — Compose-API pro měření textu před vykreslením.
  • Metoda measure() vrací TextLayoutResult s metrikami řádků a znaků.
  • API zohledňuje styl, písmo, velikost a omezení kontejneru v pixelech.
  • TextMeasurer podporuje měření jednoho řádku a víceřádkového textu.
  • Výsledek měření se používá pro vlastní rozvržení a animaci textu.

Co je TextMeasurer v Jetpack Compose?

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.

Jak funguje TextMeasurer

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í.

kotlin
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.

Měření jednoho řádku textu

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.

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)
}

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.

Měření víceřádkového textu

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.

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

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í.

TextMeasurer ve vlastních komponentách

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.

kotlin
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.

Tipy pro výkon

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

Čím se TextMeasurer liší od onSizeChanged v Compose?

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.

Lze měřit SpannableString přes TextMeasurer?

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.

Proč TextMeasurer vrací rozměry v pixelech, ne v dp?

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() }.

Jak změřit text před načtením písma?

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.

Ovlivňuje TextMeasurer výkon LazyColumn seznamů?

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í

  • TextMeasurer — Compose-API pro měření textu před vykreslením, vrací TextLayoutResult.
  • Metoda measure() přijímá text, styl a Constraints v pixelech.
  • Instance je vytvořena přes rememberTextMeasurer() a znovu používána.
  • Pro jednořádkový text se výsledek používá pro adaptivní šířku kontejneru.
  • Pro víceřádkový text — lineCount, getLineWidth() a getBoundingBox() poskytují úplný přehled.
  • Ve vlastních Layout umožňuje TextMeasurer zarovnání prvků podle prvního řádku textu.
  • Ukládejte výsledek do mezipaměti přes remember a vyhýbejte se měření na horkých cestách recompozice.

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í.

Prodiskutovat projekt

Přečtěte si také