TextMeasurer — ay isang API mula sa Jetpack Compose na idinisenyo para sa pagsukat ng mga dimensyon ng teksto bago ang aktwal na pag-render sa screen. Hindi tulad ng klasikong approach sa pamamagitan ng Paint.measureText sa Android View, ang TextMeasurer ay nagbibigay ng Compose-native na paraan upang makuha ang lapad, taas, at metrika ng mga linya na isinasaalang-alang ang estilo at mga limitasyon. Ayon sa Android Developers Documentation (2025), ang TextMeasurer ay aktibong ginagamit sa mga custom na Layout component, animated na caption, at adaptive na UI element, kung saan ang laki ng teksto ay nakakaapekto sa pagpoposisyon ng iba pang mga elemento.
Mga Pangunahing Punto
TextMeasurer — ay isang functional na interface mula sa package na androidx.compose.ui.text na nagpapahintulot ng pagsukat ng teksto sa Compose nang walang direktang pag-render sa screen. Ito ay bahagi ng text engine ng Compose at gumagana sa antas ng Paragraph API na gumagamit ng Skia para sa rendering.
Ang pangunahing layunin ng TextMeasurer ay paunang pagsukat. Sa Compose, ang laki ng teksto ay karaniwang natutukoy pagkatapos mailagay ang Composable sa Layout. Ngunit may mga sitwasyon kung saan kailangan mong malaman ang lapad ng teksto bago ang komposisyon: halimbawa, upang magpasya kung ang isang linya ay kasya sa container, o upang kalkulahin ang laki ng button batay sa haba ng teksto.
Nilulutas ng TextMeasurer ang problemang ito sa pamamagitan ng pagbibigay ng metodo na measure(), na tumatanggap ng TextMeasureRequest (teksto, estilo, limitasyon) at nagbabalik ng TextLayoutResult (metrika, linya, karakter). Pinapayagan nito ang developer na makuha ang lahat ng data tungkol sa teksto nang sabay-sabay, walang pagkaantala ng isang frame.
Gamitin ang TextMeasurer kapag ang laki ng teksto ay nakakaapekto sa laki ng parent component, kapag gumagawa ng animated na text transition, o kapag nag-iimplementa ng custom na Layout kung saan ang teksto ay katabi ng iba pang elemento.
Ang API ng TextMeasurer ay binubuo ng dalawang pangunahing entity: rememberTextMeasurer() (paglikha ng instance) at measure() (pagsisimula ng pagsukat). Ang instance ay nilikha nang isang beses para sa buong lifecycle ng component at muling ginagamit para sa lahat ng pagsukat.
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
text = "Hello, Compose!",
style = MaterialTheme.typography.bodyLarge,
constraints = Constraints(maxWidth = 200, maxHeight = 50)
)
// lapad ng resulta, taas ng resulta, bilang ng mga linya
Ang parameter na constraints ay tumutukoy sa maximum na lapad at taas kung saan dapat magkasya ang teksto. Kung hindi kasya ang teksto sa lapad, ito ay lilipat sa susunod na linya. Ang resulta ng pagsukat ay naglalaman ng TextLayoutResult, kung saan maaari mong makuha ang bilang ng mga linya, posisyon ng bawat karakter, lapad ng bawat linya, at kabuuang laki ng teksto.
Gumagana ang TextMeasurer nang sabay-sabay sa UI thread at hindi nagdudulot ng recomposition. Gayunpaman, ang madalas na pagsukat ng teksto na may iba't ibang estilo ay maaaring lumikha ng load — kaya ang instance ay nilikha sa pamamagitan ng remember at muling ginagamit.
Ang pinakasimpleng sitwasyon — sukatin ang lapad ng isang linya ng teksto upang matukoy ang laki ng parent container. Halimbawa, para sa isang button na ang lapad ay nakadepende sa haba ng label.
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)
}
Sa halimbawang ito, ang remember(label) ay ginagarantiya na ang pagsukat ay magre-restart lamang kapag nagbago ang teksto. Ang Constraints ay nakatakda na may malaking maxWidth (1000) upang maiwasan ang paglipat ng linya — ang aktwal na limitasyon ay katumbas ng lapad ng teksto. Ang lapad ng Box ay nakatakda sa sinusukat na lapad ng teksto, na nagbibigay ng epekto ng "button ayon sa laki ng teksto".
Tandaan: ang lapad mula sa resulta ay kailangang i-convert sa dp sa pamamagitan ng .dp, dahil ang TextMeasurer ay nagbabalik ng mga sukat sa pixels, habang ang Modifier.width ay umaasa ng Dp. Para sa katumpakan, gamitin ang density at LocalDensity.current.
Para sa multi-line na teksto, pinapayagan ng TextMeasurer ang pagtukoy hindi lamang ng kabuuang taas, kundi pati na rin ang bilang ng mga linya at ang posisyon ng bawat karakter sa loob ng talata. Ito ay kinakailangan kapag gumagawa ng custom na text field, chat, o editor.
Ang mga pangunahing pag-aari ng TextLayoutResult para sa multi-line na teksto: lineCount (bilang ng mga linya), getLineTop(index) (Y-coordinate ng simula ng linya), getLineBottom(index) (Y-coordinate ng dulo), getLineWidth(index) (lapad ng linya). Sa pamamagitan ng pagsasama-sama ng mga ito, maaari mong tiyak na iposisyon ang mga elemento ng interface kaugnay sa mga linya ng teksto.
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
Ang metodo na getLineWidth(lineIndex) ay nagbabalik ng lapad ng tinukoy na linya sa pixels. Maaaring mas maliit ito kaysa sa maxWidth kung ang linya ay hindi umabot sa hangganan ng container. Sa pamamagitan ng pag-alam sa lapad ng bawat linya, maaari mong manually na i-implement ang justify effect o maglagay ng inline na elemento sa tabi ng maiikling linya.
Upang makuha ang posisyon ng isang partikular na karakter, gamitin ang getBoundingBox(offset), na nagbabalik ng Rect na may mga coordinate na left, top, right, bottom. Ito ay kapaki-pakinabang para sa pagpoposisyon ng cursor sa custom na text field o para sa pag-highlight ng range ng teksto na may animation.
Ang pinakamakapangyarihang senaryo ng paggamit ng TextMeasurer — custom na Layout component, kung saan ang teksto ay katabi ng iba pang elemento, at ang kanilang mga sukat ay magkakaugnay. Halimbawa, ang widget na "Icon + text", kung saan ang icon ay dapat na naka-center kaugnay sa unang linya ng teksto, hindi sa buong block.
Sa karaniwang Row, ang icon ay naka-center sa taas ng buong Row, na mukhang hindi natural kapag ang teksto ay sumasakop ng 3 linya at ang icon ay isa lamang. Sa TextMeasurer maaari mong sukatin ang unang linya at i-align ang icon nang eksakto kaugnay dito.
val textMeasurer = rememberTextMeasurer()
Layout(
content = {
Icon(imageVector = Icons.Default.Star, contentDescription = null)
Text(text = "Multi-line na teksto dito")
},
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)
}
}
)
Sa halimbawang ito, ang pagsukat ng unang linya sa pamamagitan ng TextMeasurer ay tumutukoy sa posisyon ng icon. Kung walang TextMeasurer, ang icon ay magse-center sa buong taas ng multi-line na teksto, na biswal na hindi tama. Ang pattern na ito ay malawakang ginagamit sa mga chat, listahan ng contact, at information card.
TextMeasurer — ay isang sabay-sabay na API na gumagana sa UI thread. Sa labis na paggamit, maaari itong magdulot ng pagbaba ng frame, lalo na kapag sumusukat ng malalaking volume ng teksto sa mahihinang device.
Unang patakaran — i-cache ang resulta. Palaging balutin ang tawag sa measure() sa remember na may dependencies sa teksto at estilo. Huwag kailanman tawagan ang measure() sa loob ng CompositionLocal o sa mainit na daanan ng recomposition.
Ikalawang patakaran — limitahan ang maxWidth at maxHeight. Kung itatakda mo ang maxWidth = Constraints.Infinity, hindi maililipat ng TextMeasurer ang mga linya at lalabas ang teksto sa screen. Palaging magbigay ng makatotohanang mga limitasyon, lalo na para sa multi-line na teksto.
Ikatlo — iwasan ang paulit-ulit na pagsukat na may parehong parameter. Kung ang teksto at estilo ay hindi nagbago, ang resulta ng measure() ay magiging pareho. Pagsamahin ang pagsukat sa Deferred o LaunchedEffect para sa mga asynchronous na sitwasyon.
Mga Madalas Itanong
TextMeasurer ay sumusukat ng teksto bago mag-render, habang ang onSizeChanged ay pagkatapos. Kailangan ang TextMeasurer kapag ang laki ng teksto ay nakakaapekto sa layout ng parent; ang onSizeChanged ay angkop para sa reactive logic pagkatapos ng komposisyon.
Oo, ang TextMeasurer.measure() ay tumatanggap ng AnnotatedString na kasama ang SpannableString na may inline na estilo, link, at kulay. Lahat ng span ay isinasaalang-alang sa pagsukat.
Gumagana ang TextMeasurer sa antas ng Skia-rendering, kung saan ang lahat ng sukat ay nasa pixels. I-convert sa pamamagitan ng LocalDensity.current: density.run { width.toDp() }.
Gumagamit ang TextMeasurer ng FontFamily.Default hanggang sa ma-load ang custom na font. Para sa tumpak na pagsukat, hintaying ma-load ang font sa pamamagitan ng FontResource at gamitin ang async/await.
Ang pagsukat ng teksto sa bawat elemento ng LazyColumn ay maaaring magpababa ng FPS sa mahihinang device. Pag-optimize: i-cache ang resulta sa pamamagitan ng remember at limitahan ang laki ng teksto.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din