TextMeasurer — adalah API dari Jetpack Compose yang dirancang untuk mengukur dimensi teks sebelum benar-benar dirender di layar. Berbeda dengan pendekatan klasik melalui Paint.measureText di Android View, TextMeasurer menyediakan cara native Compose untuk mendapatkan lebar, tinggi, dan metrik baris dengan mempertimbangkan gaya dan batasan. Menurut Android Developers Documentation (2025), TextMeasurer aktif digunakan dalam komponen Layout kustom, keterangan animasi, dan elemen UI adaptif, di mana ukuran teks memengaruhi posisi elemen lainnya.
Poin Utama
TextMeasurer — adalah antarmuka fungsional dari paket androidx.compose.ui.text yang memungkinkan pengukuran teks di Compose tanpa rendering langsung di layar. Ini adalah bagian dari mesin teks Compose dan bekerja pada tingkat Paragraph API yang menggunakan Skia untuk rendering.
Tujuan utama TextMeasurer adalah pengukuran awal. Di Compose, ukuran teks biasanya ditentukan setelah Composable ditempatkan di Layout. Namun ada skenario di mana Anda perlu mengetahui lebar teks sebelum komposisi: misalnya, untuk memutuskan apakah baris muat di wadah, atau untuk menghitung ukuran tombol berdasarkan panjang teks.
TextMeasurer memecahkan masalah ini dengan menyediakan metode measure(), yang menerima TextMeasureRequest (teks, gaya, batasan) dan mengembalikan TextLayoutResult (metrik, baris, karakter). Ini memungkinkan pengembang mendapatkan semua data tentang teks secara sinkron, tanpa penundaan satu frame.
Gunakan TextMeasurer ketika ukuran teks memengaruhi ukuran komponen induk, saat membuat transisi teks animasi, atau saat mengimplementasikan Layout kustom di mana teks berdampingan dengan elemen lain.
API TextMeasurer terdiri dari dua entitas kunci: rememberTextMeasurer() (pembuatan instance) dan measure() (memulai pengukuran). Instance dibuat sekali untuk seluruh siklus hidup komponen dan digunakan kembali untuk semua pengukuran.
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
text = "Hello, Compose!",
style = MaterialTheme.typography.bodyLarge,
constraints = Constraints(maxWidth = 200, maxHeight = 50)
)
// lebar hasil, tinggi hasil, jumlah baris
Parameter constraints menentukan lebar dan tinggi maksimum di mana teks harus muat. Jika teks tidak muat secara lebar, ia akan dipindahkan ke baris berikutnya. Hasil pengukuran berisi TextLayoutResult, di mana Anda bisa mendapatkan jumlah baris, posisi setiap karakter, lebar setiap baris, dan ukuran total teks.
TextMeasurer bekerja secara sinkron di thread UI dan tidak menyebabkan rekomposisi. Namun, pengukuran teks yang sering dengan gaya berbeda dapat menimbulkan beban — oleh karena itu instance dibuat melalui remember dan digunakan kembali.
Skenario paling sederhana — mengukur lebar teks satu baris untuk menentukan ukuran wadah induk. Misalnya, untuk tombol yang lebarnya tergantung pada panjang 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)
}
Dalam contoh ini, remember(label) memastikan bahwa pengukuran hanya dimulai ulang saat teks berubah. Constraints diatur dengan maxWidth besar (1000) untuk mencegah pemindahan baris — batasan sebenarnya akan sama dengan lebar teks. Lebar Box diatur ke lebar teks yang diukur, memberikan efek "tombol sesuai ukuran teks".
Perhatikan: lebar dari hasil perlu dikonversi ke dp melalui .dp, karena TextMeasurer mengembalikan ukuran dalam piksel, sedangkan Modifier.width mengharapkan Dp. Untuk akurasi, gunakan density dan LocalDensity.current.
Untuk teks multi-baris, TextMeasurer memungkinkan penentuan tidak hanya tinggi total, tetapi juga jumlah baris dan posisi setiap karakter di dalam paragraf. Ini diperlukan saat membuat bidang teks kustom, obrolan, atau editor.
Properti utama TextLayoutResult untuk teks multi-baris: lineCount (jumlah baris), getLineTop(index) (koordinat Y awal baris), getLineBottom(index) (koordinat Y akhir), getLineWidth(index) (lebar baris). Dengan menggabungkannya, Anda dapat memposisikan elemen antarmuka secara tepat relatif terhadap baris teks.
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
Metode getLineWidth(lineIndex) mengembalikan lebar baris yang ditentukan dalam piksel. Ini bisa lebih kecil dari maxWidth jika baris tidak mencapai batas wadah. Mengetahui lebar setiap baris, Anda dapat menerapkan efek rata kanan-kiri secara manual atau menempatkan elemen inline di samping baris pendek.
Untuk mendapatkan posisi karakter tertentu, gunakan getBoundingBox(offset), yang mengembalikan Rect dengan koordinat left, top, right, bottom. Ini berguna untuk memposisikan kursor di bidang teks kustom atau untuk menyorot rentang teks dengan animasi.
Skenario penggunaan TextMeasurer yang paling kuat — komponen Layout kustom, di mana teks berdampingan dengan elemen lain, dan ukurannya saling bergantung. Misalnya, widget "Ikon + teks", di mana ikon harus dipusatkan relatif terhadap baris pertama teks, bukan seluruh blok.
Di Row standar, ikon dipusatkan berdasarkan tinggi seluruh Row, yang terlihat tidak alami ketika teks memakan 3 baris dan ikon hanya satu. Dengan TextMeasurer Anda dapat mengukur baris pertama dan menyelaraskan ikon tepat relatif terhadapnya.
val textMeasurer = rememberTextMeasurer()
Layout(
content = {
Icon(imageVector = Icons.Default.Star, contentDescription = null)
Text(text = "Teks multi-baris di sini")
},
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)
}
}
)
Dalam contoh ini, pengukuran baris pertama melalui TextMeasurer menentukan posisi ikon. Tanpa TextMeasurer, ikon akan dipusatkan di seluruh tinggi teks multi-baris, yang secara visual tidak benar. Pola ini banyak digunakan dalam obrolan, daftar kontak, dan kartu informasi.
TextMeasurer — API sinkron yang bekerja di thread UI. Saat digunakan secara berlebihan, dapat menyebabkan penurunan frame, terutama saat mengukur volume teks besar di perangkat yang lemah.
Aturan pertama — cache hasilnya. Selalu bungkus panggilan measure() dalam remember dengan dependensi pada teks dan gaya. Jangan pernah memanggil measure() di dalam CompositionLocal atau di jalur rekomposisi yang panas.
Aturan kedua — batasi maxWidth dan maxHeight. Jika Anda mengatur maxWidth = Constraints.Infinity, TextMeasurer tidak akan bisa memindahkan baris dan teks akan keluar dari layar. Selalu berikan batasan yang realistis, terutama untuk teks multi-baris.
Aturan ketiga — hindari pengukuran berulang dengan parameter yang sama. Jika teks dan gaya tidak berubah, hasil measure() akan sama. Gabungkan pengukuran dengan Deferred atau LaunchedEffect untuk skenario asinkron.
Pertanyaan Umum
TextMeasurer mengukur teks sebelum rendering, sedangkan onSizeChanged setelahnya. TextMeasurer diperlukan ketika ukuran teks memengaruhi tata letak induk; onSizeChanged cocok untuk logika reaktif setelah komposisi.
Ya, TextMeasurer.measure() menerima AnnotatedString yang mencakup SpannableString dengan gaya inline, tautan, dan warna. Semua span diperhitungkan saat pengukuran.
TextMeasurer bekerja pada tingkat rendering Skia, di mana semua ukuran dalam piksel. Konversikan melalui LocalDensity.current: density.run { width.toDp() }.
TextMeasurer menggunakan FontFamily.Default sampai font kustom dimuat. Untuk pengukuran akurat, tunggu pemuatan melalui FontResource dan gunakan async/await.
Mengukur teks di setiap elemen LazyColumn dapat menurunkan FPS di perangkat yang lemah. Optimasi: cache hasilnya melalui remember dan batasi ukuran teks.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga