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 узима у обзир стил, фонт, величину и ограничења контејнера у пикселима.
  • 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 враћа димензије у пикселима, а 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) враћа ширину наведеног реда у пикселима. Може бити мања од maxWidth ако ред не досеже до границе контејнера. Знајући ширину сваког реда, можете ручно имплементирати ефекат поравнања или поставити инлине елементе поред кратких редова.

Да бисте добили позицију одређеног знака, користите getBoundingBox(offset), који враћа Rect са координатама left, top, right, bottom. Ово је корисно за позиционирање курсора у прилагођеним текст-пољима или за истицање опсега текста анимацијом.

TextMeasurer у прилагођеним компонентама

Најмоћнији сценарио примене TextMeasurer-а — прилагођене Layout компоненте, где текст граничи са другим елементима и њихове величине су међусобно зависне. На пример, виџет „Иконица + текст", где иконица треба да буде центрирана у односу на први ред текста, а не на цео блок.

У стандардном 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 је потребан када величина текста утиче на layout родитеља; onSizeChanged је погодан за реактивну логику након композиције.

Може ли се SpannableString измерити кроз TextMeasurer?

Да, TextMeasurer.measure() прима AnnotatedString, који укључује SpannableString са инлине-стиловима, везама и бојама. Сви спанови се узимају у обзир при мерењу.

Зашто TextMeasurer враћа величину у пикселима, а не у dp?

TextMeasurer ради на нивоу Skia-рендеровања, где су све величине у пикселима. Конвертујте кроз LocalDensity.current: density.run { width.toDp() }.

Како измерити текст пре него што се фонт учита?

TextMeasurer користи FontFamily.Default до учитавања прилагођеног фонта. За прецизно мерење сачекајте учитавање кроз FontResource и користите async/await.

Да ли TextMeasurer утиче на перформансе LazyColumn листи?

Мерење текста у сваком елементу LazyColumn-а може смањити FPS на слабијим уређајима. Оптимизација: кеширајте резултат кроз remember и ограничите величину текста.

Закључак

  • TextMeasurer — Compose-API за мерење текста пре рендеровања, враћа TextLayoutResult.
  • Метод measure() прима текст, стил и Constraints у пикселима.
  • Инстанца се креира кроз rememberTextMeasurer() и поново користи.
  • За једноредни текст резултат се користи за адаптивну ширину контејнера.
  • За вишередни текст — lineCount, getLineWidth() и getBoundingBox() дају потпуну слику.
  • У прилагођеним Layout-овима TextMeasurer омогућава поравнање елемената по првом реду текста.
  • Кеширајте резултат кроз remember и избегавајте мерење на врућим путањама рекомпозиције.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође