TextMeasurer: co to jest i jak działa mierzenie tekstu w Compose

Autor: IT Sectr Opublikowano: 2026-07-25 Czas czytania: 8 min

TextMeasurer — to API z Jetpack Compose przeznaczone do mierzenia rozmiarów tekstu przed jego faktycznym wyrenderowaniem na ekranie. W przeciwieństwie do klasycznego podejścia przez Paint.measureText w Android View, TextMeasurer zapewnia natywny dla Compose sposób uzyskania szerokości, wysokości i metryk wierszy z uwzględnieniem stylu i ograniczeń. Według Android Developers Documentation (2025), TextMeasurer jest aktywnie używany w niestandardowych komponentach Layout, animowanych podpisach i adaptacyjnych elementach UI, gdzie rozmiar tekstu wpływa na pozycjonowanie innych elementów.

Najważniejsze

  • TextMeasurer — Compose-API do mierzenia tekstu przed renderowaniem.
  • Metoda measure() zwraca TextLayoutResult z metrykami wierszy i znaków.
  • API uwzględnia styl, czcionkę, rozmiar i ograniczenia kontenera w pikselach.
  • TextMeasurer obsługuje mierzenie pojedynczego wiersza i tekstu wielowierszowego.
  • Wynik pomiaru jest używany do niestandardowego układu i animacji tekstu.

Co to jest TextMeasurer w Jetpack Compose?

TextMeasurer — to funkcyjny interfejs z pakietu androidx.compose.ui.text, który umożliwia mierzenie tekstu w Compose bez bezpośredniego renderowania na ekranie. Jest częścią silnika tekstowego Compose i działa na poziomie Paragraph API, który wykorzystuje Skia do renderowania.

Głównym przeznaczeniem TextMeasurer jest pomiar wstępny. W Compose rozmiar tekstu jest zwykle określany po tym, jak Composable został już umieszczony w Layout. Istnieją jednak scenariusze, w których trzeba znać szerokość tekstu przed kompozycją: na przykład, aby zdecydować, czy wiersz mieści się w kontenerze, lub aby obliczyć rozmiar przycisku na podstawie długości tekstu.

TextMeasurer rozwiązuje ten problem, udostępniając metodę measure(), która przyjmuje TextMeasureRequest (tekst, styl, ograniczenia) i zwraca TextLayoutResult (metryki, wiersze, znaki). Pozwala to programiście uzyskać wszystkie dane o tekście synchronicznie, bez opóźnienia o jedną klatkę.

Używaj TextMeasurer, gdy rozmiar tekstu wpływa na rozmiar komponentu nadrzędnego, podczas tworzenia animowanych przejść tekstowych lub przy implementacji niestandardowych Layout, gdzie tekst sąsiaduje z innymi elementami.

Jak działa TextMeasurer

API TextMeasurer składa się z dwóch kluczowych elementów: rememberTextMeasurer() (tworzenie instancji) i measure() (uruchomienie pomiaru). Instancja jest tworzona raz na cały cykl życia komponentu i ponownie używana do wszystkich pomiarów.

kotlin
val textMeasurer = rememberTextMeasurer()

val result = textMeasurer.measure(
    text = "Hello, Compose!",
    style = MaterialTheme.typography.bodyLarge,
    constraints = Constraints(maxWidth = 200, maxHeight = 50)
)

// szerokość wyniku, wysokość wyniku, liczba wierszy

Parametr constraints określa maksymalną szerokość i wysokość, w ramach których tekst musi się zmieścić. Jeśli tekst nie mieści się w szerokości, jest zawijany do następnego wiersza. Wynik pomiaru zawiera TextLayoutResult, w którym można uzyskać liczbę wierszy, pozycję każdego znaku, szerokość każdego wiersza i całkowity rozmiar tekstu.

TextMeasurer działa synchronicznie w wątku UI i nie powoduje ponownej kompozycji. Jednak częste mierzenie tekstu z różnymi stylami może obciążać system — dlatego instancja jest tworzona przez remember i ponownie używana.

Mierzenie pojedynczego wiersza tekstu

Najprostszy scenariusz — zmierzyć szerokość jednowierszowego tekstu, aby określić rozmiar kontenera nadrzędnego. Na przykład dla przycisku, którego szerokość zależy od długości etykiety.

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

W tym przykładzie remember(label) gwarantuje, że pomiar jest restartowany tylko przy zmianie tekstu. Constraints są ustawione z dużym maxWidth (1000), aby zapobiec zawijaniu wiersza — faktyczne ograniczenie będzie równe szerokości tekstu. Szerokość Box jest ustawiana na zmierzoną szerokość tekstu, co daje efekt „przycisku dopasowanego do tekstu”.

Uwaga: szerokość z wyniku należy przekonwertować na dp przez .dp, ponieważ TextMeasurer zwraca rozmiary w pikselach, a Modifier.width oczekuje Dp. Dla dokładności użyj density i LocalDensity.current.

Mierzenie tekstu wielowierszowego

Dla tekstu wielowierszowego TextMeasurer pozwala określić nie tylko całkowitą wysokość, ale także liczbę wierszy oraz położenie każdego znaku w akapicie. Jest to niezbędne przy tworzeniu niestandardowych pól tekstowych, czatów lub edytorów.

Główne właściwości TextLayoutResult dla tekstu wielowierszowego: lineCount (liczba wierszy), getLineTop(index) (współrzędna Y początku wiersza), getLineBottom(index) (współrzędna Y końca), getLineWidth(index) (szerokość wiersza). Łącząc je, można precyzyjnie pozycjonować elementy interfejsu względem wierszy tekstu.

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) zwraca szerokość określonego wiersza w pikselach. Może być mniejsza niż maxWidth, jeśli wiersz nie sięga granicy kontenera. Znając szerokość każdego wiersza, można ręcznie zaimplementować efekt justowania lub umieścić elementy inline obok krótkich wierszy.

Aby uzyskać pozycję konkretnego znaku, użyj getBoundingBox(offset), który zwraca Rect ze współrzędnymi left, top, right, bottom. Jest to przydatne do pozycjonowania kursora w niestandardowych polach tekstowych lub do podświetlania zakresu tekstu animacją.

TextMeasurer w niestandardowych komponentach

Najbardziej zaawansowanym scenariuszem użycia TextMeasurer są niestandardowe komponenty Layout, w których tekst sąsiaduje z innymi elementami, a ich rozmiary są współzależne. Na przykład widget „Ikona + tekst”, w którym ikona powinna być wyśrodkowana względem pierwszego wiersza tekstu, a nie całego bloku.

W standardowym Row ikona jest centrowana względem wysokości całego Row, co wygląda nienaturalnie, gdy tekst zajmuje 3 wiersze, a ikona — jeden. Z TextMeasurer można zmierzyć pierwszy wiersz i wyrównać ikonę właśnie względem niego.

kotlin
val textMeasurer = rememberTextMeasurer()

Layout(
    content = {
        Icon(imageVector = Icons.Default.Star, contentDescription = null)
        Text(text = "Tekst wielowierszowy tutaj")
    },
    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)
        }
    }
)

W tym przykładzie pomiar pierwszego wiersza przez TextMeasurer określa pozycję ikony. Bez TextMeasurer ikona byłaby centrowana względem całej wysokości tekstu wielowierszowego, co jest wizualnie niepoprawne. Ten wzorzec jest szeroko stosowany w czatach, listach kontaktów i kartach informacyjnych.

Porady dotyczące wydajności

TextMeasurer — to synchroniczne API działające w wątku UI. Przy nadmiernym użyciu może powodować spadki klatek, szczególnie przy mierzeniu dużych ilości tekstu na słabszych urządzeniach.

Pierwsza zasada — zapisuj wynik w pamięci podręcznej. Zawsze opakowuj wywołanie measure() w remember z zależnościami od tekstu i stylu. Nigdy nie wywołuj measure() wewnątrz CompositionLocal ani na gorących ścieżkach rekompozycji.

Druga zasada — ograniczaj maxWidth i maxHeight. Jeśli ustawisz maxWidth = Constraints.Infinity, TextMeasurer nie będzie mógł zawijać wierszy i tekst wyjdzie poza ekran. Zawsze przekazuj realistyczne ograniczenia, szczególnie dla tekstu wielowierszowego.

Trzecia — unikaj ponownego mierzenia z tymi samymi parametrami. Jeśli tekst i styl się nie zmieniły, wynik measure() będzie taki sam. Łącz pomiar z Deferred lub LaunchedEffect dla scenariuszy asynchronicznych.

Często zadawane pytania

Czym TextMeasurer różni się od onSizeChanged w Compose?

TextMeasurer mierzy tekst przed renderowaniem, a onSizeChanged — po. TextMeasurer jest potrzebny, gdy rozmiar tekstu wpływa na układ rodzica; onSizeChanged nadaje się do reaktywnej logiki po kompozycji.

Czy można zmierzyć SpannableString przez TextMeasurer?

Tak, TextMeasurer.measure() przyjmuje AnnotatedString, który zawiera SpannableString z stylami inline, linkami i kolorami. Wszystkie spany są uwzględniane przy pomiarze.

Dlaczego TextMeasurer zwraca rozmiar w pikselach, a nie w dp?

TextMeasurer działa na poziomie Skia-renderowania, gdzie wszystkie rozmiary są w pikselach. Konwertuj przez LocalDensity.current: density.run { width.toDp() }.

Jak zmierzyć tekst przed załadowaniem czcionki?

TextMeasurer używa FontFamily.Default do momentu załadowania niestandardowej czcionki. Dla dokładnego pomiaru poczekaj na załadowanie przez FontResource i użyj async/await.

Czy TextMeasurer wpływa na wydajność list LazyColumn?

Mierzenie tekstu w każdym elemencie LazyColumn może obniżać FPS na słabszych urządzeniach. Optymalizacja: zapisuj w pamięci podręcznej wynik przez remember i ograniczaj rozmiar tekstu.

Podsumowanie

  • TextMeasurer — Compose-API do mierzenia tekstu przed renderowaniem, zwraca TextLayoutResult.
  • Metoda measure() przyjmuje tekst, styl i Constraints w pikselach.
  • Instancja jest tworzona przez rememberTextMeasurer() i ponownie używana.
  • Dla tekstu jednowierszowego wynik jest używany do adaptacyjnej szerokości kontenera.
  • Dla tekstu wielowierszowego — lineCount, getLineWidth() i getBoundingBox() dają pełny obraz.
  • W niestandardowych Layout TextMeasurer pozwala wyrównać elementy względem pierwszego wiersza tekstu.
  • Zapisuj wynik w pamięci podręcznej przez remember i unikaj pomiarów na gorących ścieżkach rekompozycji.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również