TextMeasurer — egy API a Jetpack Compose-ból, amely a szöveg méreteinek mérésére szolgál a képernyőn történő tényleges megjelenítés előtt. Ellentétben a klasszikus megközelítéssel, amely a Paint.measureText-et használja Android View-ban, a TextMeasurer egy Compose-natív módszert biztosít a sorok szélességének, magasságának és metrikáinak megszerzésére, figyelembe véve a stílust és a korlátozásokat. Az Android Developers Documentation (2025) szerint a TextMeasurer aktívan használatos egyedi Layout komponensekben, animált feliratokban és adaptív UI elemekben, ahol a szöveg mérete befolyásolja más elemek pozicionálását.
Főbb pontok
TextMeasurer — egy funkcionális interfész az androidx.compose.ui.text csomagból, amely lehetővé teszi a szöveg mérését a Compose-ban anélkül, hogy közvetlenül megjelenítené a képernyőn. Része a Compose szövegmotorjának, és a Paragraph API szintjén működik, amely a Skia-t használja a rendereléshez.
A TextMeasurer fő célja az előzetes mérés. A Compose-ban a szöveg mérete általában azután kerül meghatározásra, hogy a Composable már elhelyezésre került a Layout-ban. De vannak olyan forgatókönyvek, ahol a szöveg szélességét a kompozíció előtt kell tudni: például annak eldöntéséhez, hogy egy sor befér-e a konténerbe, vagy a gomb méretének kiszámításához a szöveg hossza alapján.
A TextMeasurer ezt a problémát a measure() metódus biztosításával oldja meg, amely fogadja a TextMeasureRequest-ot (szöveg, stílus, korlátozások) és visszaadja a TextLayoutResult-ot (metrikák, sorok, karakterek). Ez lehetővé teszi a fejlesztő számára, hogy a szövegről minden adatot szinkron módon, egy képkocka késleltetés nélkül megszerezzen.
Használja a TextMeasurer-t, amikor a szöveg mérete befolyásolja a szülőkomponens méretét, animált szövegátmenetek létrehozásakor, vagy egyedi Layout-ok implementálásakor, ahol a szöveg más elemekkel szomszédos.
A TextMeasurer API két kulcsfontosságú entitásból áll: rememberTextMeasurer() (példány létrehozása) és measure() (mérés elindítása). A példány egyszer jön létre a komponens teljes életciklusára, és újra felhasználható az összes méréshez.
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
text = "Hello, Compose!",
style = MaterialTheme.typography.bodyLarge,
constraints = Constraints(maxWidth = 200, maxHeight = 50)
)
// eredmény szélessége, eredmény magassága, sorok száma
A constraints paraméter határozza meg a maximális szélességet és magasságot, amelyen belül a szövegnek el kell férnie. Ha a szöveg nem fér el szélességben, a következő sorba kerül. A mérési eredmény tartalmazza a TextLayoutResult-ot, amelyben lekérhető a sorok száma, az egyes karakterek pozíciója, az egyes sorok szélessége és a szöveg teljes mérete.
A TextMeasurer szinkron módon működik az UI szálban, és nem okoz újrakomponálást. Azonban a szöveg gyakori mérése különböző stílusokkal terhelést okozhat — ezért a példány a remember-en keresztül jön létre és használható újra.
A legegyszerűbb forgatókönyv — mérje meg az egysoros szöveg szélességét a szülő konténer méretének meghatározásához. Például egy gomb esetében, amelynek szélessége a felirat hosszától függ.
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)
}
Ebben a példában a remember(label) garantálja, hogy a mérés csak a szöveg változásakor indul újra. A Constraints nagy maxWidth (1000) értékkel van beállítva a sortörés megelőzésére — a tényleges korlátozás megegyezik a szöveg szélességével. A Box szélessége a mért szövegszélességre van állítva, ami a „gomb a szöveg méretéhez igazítva" effektust adja.
Figyelem: az eredményből származó szélességet .dp-n keresztül dp-vé kell konvertálni, mivel a TextMeasurer pixelekben adja vissza a méreteket, míg a Modifier.width Dp-t vár. A pontosság érdekében használja a density-t és a LocalDensity.current-et.
Többsoros szöveg esetén a TextMeasurer lehetővé teszi nemcsak a teljes magasság, hanem a sorok számának és az egyes karakterek pozíciójának meghatározását is a bekezdésen belül. Ez szükséges egyedi szövegmezők, csevegések vagy szerkesztők létrehozásakor.
A TextLayoutResult fő tulajdonságai többsoros szöveghez: lineCount (sorok száma), getLineTop(index) (a sor kezdetének Y-koordinátája), getLineBottom(index) (a sor végének Y-koordinátája), getLineWidth(index) (a sor szélessége). Ezek kombinálásával pontosan pozícionálhatja a felület elemeit a szövegsorokhoz viszonyítva.
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
A getLineWidth(lineIndex) metódus a megadott sor szélességét adja vissza pixelben. Kisebb lehet, mint a maxWidth, ha a sor nem éri el a konténer határát. Az egyes sorok szélességének ismeretében manuálisan implementálhatja a sorkizárt effektust, vagy inline elemeket helyezhet el a rövid sorok mellé.
Egy adott karakter pozíciójának lekéréséhez használja a getBoundingBox(offset)-ot, amely egy Rect-et ad vissza left, top, right, bottom koordinátákkal. Ez hasznos a kurzor pozicionálásához egyedi szövegmezőkben, vagy egy szövegtartomány animációval történő kiemeléséhez.
A TextMeasurer legerőteljesebb alkalmazási forgatókönyve — egyedi Layout komponensek, ahol a szöveg más elemekkel szomszédos, és méreteik kölcsönösen függnek egymástól. Például az „Ikon + szöveg" widget, ahol az ikonnak a szöveg első sorához képest kell középre igazodnia, nem a teljes blokkhoz.
A szabványos Row-ban az ikon a teljes Row magasságához képest középre igazodik, ami természetellenesnek tűnik, amikor a szöveg 3 sort foglal el, az ikon pedig egyet. A TextMeasurer-rel megmérheti az első sort, és pontosan ahhoz igazíthatja az ikont.
val textMeasurer = rememberTextMeasurer()
Layout(
content = {
Icon(imageVector = Icons.Default.Star, contentDescription = null)
Text(text = "Többsoros szöveg itt")
},
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)
}
}
)
Ebben a példában az első sor TextMeasurer-en keresztüli mérése határozza meg az ikon pozícióját. TextMeasurer nélkül az ikon a többsoros szöveg teljes magasságához képest középre igazodna, ami vizuálisan helytelen. Ezt a mintát széles körben használják csevegésekben, névjegyzékekben és információ kártyákon.
A TextMeasurer — egy szinkron API, amely az UI szálban működik. Túlzott használat esetén képkocka kiesést okozhat, különösen nagy mennyiségű szöveg mérésekor gyengébb eszközökön.
Első szabály — gyorsítótárazza az eredményt. Mindig csomagolja a measure() hívást remember-be a szövegtől és stílustól függően. Soha ne hívja a measure()-t CompositionLocal-on belül vagy a forró újrakomponálási útvonalakon.
Második szabály — korlátozza a maxWidth és maxHeight értékeket. Ha a maxWidth = Constraints.Infinity értéket állítja be, a TextMeasurer nem tud sortörést végezni, és a szöveg kilóg a képernyőről. Mindig adjon meg reális korlátozásokat, különösen többsoros szöveg esetén.
Harmadik — kerülje a mérés megismétlését ugyanazokkal a paraméterekkel. Ha a szöveg és a stílus nem változott, a measure() eredménye ugyanaz lesz. Kombinálja a mérést Deferred vagy LaunchedEffect segítségével aszinkron forgatókönyvekhez.
Gyakran ismételt kérdések
A TextMeasurer a renderelés előtt méri a szöveget, míg az onSizeChanged utána. A TextMeasurer akkor szükséges, amikor a szöveg mérete befolyásolja a szülő layout-ját; az onSizeChanged a kompozíció utáni reaktív logikához alkalmas.
Igen, a TextMeasurer.measure() fogadja az AnnotatedString-et, amely inline stílusokkal, hivatkozásokkal és színekkel ellátott SpannableString-et tartalmaz. Az összes span figyelembevételre kerül a mérés során.
A TextMeasurer a Skia-renderelés szintjén működik, ahol minden méret pixelben van. Konvertálja a LocalDensity.current-en keresztül: density.run { width.toDp() }.
A TextMeasurer FontFamily.Default-ot használ az egyedi betűtípus betöltéséig. A pontos méréshez várja meg a betöltést a FontResource-on keresztül, és használja az async/awaitet.
A szöveg mérése a LazyColumn minden elemében csökkentheti az FPS-t gyengébb eszközökön. Optimalizálás: gyorsítótárazza az eredményt a remember-en keresztül, és korlátozza a szöveg méretét.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is