TextMeasurer: nó là gì và cách đo lường văn bản trong Compose hoạt động

Tác giả: IT Sectr Đã đăng: 2026-07-25 Thời gian đọc: 8 phút

TextMeasurer là một API từ Jetpack Compose được thiết kế để đo kích thước văn bản trước khi nó thực sự được hiển thị trên màn hình. Khác với cách tiếp cận cổ điển qua Paint.measureText trong Android View, TextMeasurer cung cấp một cách Compose-native để lấy chiều rộng, chiều cao và các chỉ số dòng có tính đến kiểu và ràng buộc. Theo Android Developers Documentation (2025), TextMeasurer được sử dụng tích cực trong các thành phần Layout tùy chỉnh, nhãn hoạt hình và các yếu tố UI thích nghi nơi kích thước văn bản ảnh hưởng đến vị trí của các yếu tố khác.

Điểm Chính

  • TextMeasurer — API Compose để đo văn bản trước khi hiển thị.
  • Phương thức measure() trả về TextLayoutResult với các chỉ số dòng và ký tự.
  • API tính đến kiểu, phông chữ, kích thước và ràng buộc của container bằng pixel.
  • TextMeasurer hỗ trợ đo một dòng và văn bản nhiều dòng.
  • Kết quả đo được sử dụng cho bố cục tùy chỉnh và hoạt hình văn bản.

TextMeasurer trong Jetpack Compose là gì?

TextMeasurer là một giao diện chức năng từ gói androidx.compose.ui.text cho phép đo văn bản trong Compose mà không cần hiển thị thực tế trên màn hình. Nó là một phần của công cụ văn bản Compose và hoạt động ở cấp độ Paragraph API, sử dụng Skia để hiển thị.

Mục đích chính của TextMeasurer là đo trước. Trong Compose, kích thước văn bản thường được xác định sau khi Composable đã được đặt trong Layout. Tuy nhiên, có những tình huống bạn cần biết chiều rộng của văn bản trước khi bố cục — ví dụ, để quyết định một chuỗi có vừa với container hay không, hoặc để tính kích thước nút dựa trên độ dài văn bản.

TextMeasurer giải quyết điều này bằng cách cung cấp phương thức measure(), nhận TextMeasureRequest (văn bản, kiểu, ràng buộc) và trả về TextLayoutResult (chỉ số, dòng, ký tự). Điều này cho phép nhà phát triển lấy tất cả dữ liệu văn bản một cách đồng bộ, không bị trễ một khung hình.

Sử dụng TextMeasurer khi kích thước văn bản ảnh hưởng đến kích thước thành phần cha, khi tạo chuyển tiếp văn bản hoạt hình, hoặc khi triển khai Layout tùy chỉnh nơi văn bản cùng tồn tại với các yếu tố khác.

Cách TextMeasurer hoạt động

API TextMeasurer bao gồm hai thực thể chính: rememberTextMeasurer() (tạo thể hiện) và measure() (chạy đo lường). Thể hiện được tạo một lần cho toàn bộ vòng đời thành phần và được tái sử dụng cho tất cả các phép đo.

kotlin
val textMeasurer = rememberTextMeasurer()

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

// result.size.width, result.size.height, result.lineCount

Tham số constraints xác định chiều rộng và chiều cao tối đa mà văn bản phải nằm gọn. Nếu văn bản không vừa với chiều rộng, nó sẽ xuống dòng tiếp theo. Kết quả đo chứa TextLayoutResult, cung cấp số dòng, vị trí của từng ký tự, chiều rộng của từng dòng và kích thước tổng thể của văn bản.

TextMeasurer hoạt động đồng bộ trên luồng UI và không kích hoạt tái hợp thành. Tuy nhiên, việc đo văn bản thường xuyên với các kiểu khác nhau có thể tạo ra chi phí — vì vậy thể hiện được tạo qua remember và tái sử dụng.

Đo văn bản một dòng

Kịch bản đơn giản nhất là đo chiều rộng của văn bản một dòng để xác định kích thước container cha. Ví dụ, cho một nút có chiều rộng phụ thuộc vào độ dài nhãn.

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

Trong ví dụ này, remember(label) đảm bảo phép đo chỉ khởi động lại khi văn bản thay đổi. Các ràng buộc được đặt với maxWidth lớn (1000) để ngăn xuống dòng — ràng buộc thực tế sẽ bằng chiều rộng văn bản. Chiều rộng của Box được đặt bằng chiều rộng đo được của văn bản, tạo hiệu ứng “nút vừa với văn bản”.

Lưu ý: chiều rộng từ kết quả cần được chuyển đổi thành dp qua .dp, vì TextMeasurer trả về kích thước bằng pixel, trong khi Modifier.width mong đợi Dp. Để chính xác, hãy sử dụng density và LocalDensity.current.

Đo văn bản nhiều dòng

Đối với văn bản nhiều dòng, TextMeasurer cho phép xác định không chỉ tổng chiều cao mà còn số dòng và vị trí của từng ký tự trong đoạn văn. Điều này cần thiết khi tạo các trường văn bản tùy chỉnh, chat hoặc trình soạn thảo.

Các thuộc tính TextLayoutResult chính cho văn bản nhiều dòng: lineCount (số dòng), getLineTop(index) (tọa độ Y đầu dòng), getLineBottom(index) (tọa độ Y cuối dòng), getLineWidth(index) (chiều rộng dòng). Kết hợp chúng, bạn có thể định vị chính xác các yếu tố giao diện so với các dòng văn bản.

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

Phương thức getLineWidth(lineIndex) trả về chiều rộng của dòng được chỉ định bằng pixel. Nó có thể nhỏ hơn maxWidth nếu dòng không chạm đến ranh giới container. Biết chiều rộng của từng dòng, bạn có thể thực hiện hiệu ứng căn đều thủ công hoặc đặt các phần tử inline bên cạnh các dòng ngắn.

Để lấy vị trí của một ký tự cụ thể, hãy sử dụng getBoundingBox(offset), trả về Rect với các tọa độ left, top, right, bottom. Điều này hữu ích cho việc định vị con trỏ trong các trường văn bản tùy chỉnh hoặc làm nổi bật một phạm vi văn bản bằng hoạt hình.

TextMeasurer trong các thành phần tùy chỉnh

Ứng dụng mạnh nhất của TextMeasurer là các thành phần Layout tùy chỉnh, nơi văn bản cùng tồn tại với các yếu tố khác và kích thước của chúng phụ thuộc lẫn nhau. Ví dụ, widget “Biểu tượng + văn bản” nơi biểu tượng phải được căn giữa so với dòng đầu tiên của văn bản, không phải toàn bộ khối.

Trong Row tiêu chuẩn, biểu tượng được căn giữa theo toàn bộ chiều cao của Row, trông không tự nhiên khi văn bản chiếm 3 dòng trong khi biểu tượng chỉ một. Với TextMeasurer, bạn có thể đo dòng đầu tiên và căn chỉnh biểu tượng chính xác với nó.

kotlin
val textMeasurer = rememberTextMeasurer()

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

Trong ví dụ này, việc đo dòng đầu tiên qua TextMeasurer xác định vị trí của biểu tượng. Không có TextMeasurer, biểu tượng sẽ được căn giữa theo toàn bộ chiều cao của văn bản nhiều dòng, điều này không đúng về mặt thị giác. Mẫu này được sử dụng rộng rãi trong chat, danh sách liên hệ và thẻ thông tin.

Mẹo hiệu suất

TextMeasurer là API đồng bộ chạy trên luồng UI. Việc sử dụng quá mức có thể gây tụt khung hình, đặc biệt khi đo khối lượng văn bản lớn trên các thiết bị yếu.

Quy tắc đầu tiên — lưu vùng đệm kết quả. Luôn bao bọc lời gọi measure() trong remember với các phụ thuộc văn bản và kiểu. Không bao giờ gọi measure() bên trong CompositionLocal hoặc trong các đường tái hợp thành nóng.

Quy tắc thứ hai — giới hạn maxWidth và maxHeight. Nếu bạn đặt maxWidth = Constraints.Infinity, TextMeasurer sẽ không thể xuống dòng và văn bản sẽ tràn ra ngoài màn hình. Luôn truyền các ràng buộc thực tế, đặc biệt cho văn bản nhiều dòng.

Quy tắc thứ ba — tránh đo lặp lại với cùng tham số. Nếu văn bản và kiểu không thay đổi, kết quả của measure() sẽ giống nhau. Kết hợp đo lường với Deferred hoặc LaunchedEffect cho các kịch bản bất đồng bộ.

Câu hỏi thường gặp

TextMeasurer khác gì so với onSizeChanged trong Compose?

TextMeasurer đo văn bản trước khi hiển thị, trong khi onSizeChanged đo sau. TextMeasurer cần thiết khi kích thước văn bản ảnh hưởng đến bố cục cha; onSizeChanged phù hợp cho logic phản ứng sau khi hợp thành.

Có thể đo SpannableString qua TextMeasurer không?

Có, TextMeasurer.measure() chấp nhận AnnotatedString, bao gồm SpannableString với kiểu inline, liên kết và màu sắc. Tất cả các span đều được tính đến khi đo.

Tại sao TextMeasurer trả về kích thước bằng pixel thay vì dp?

TextMeasurer hoạt động ở cấp độ kết xuất Skia, nơi tất cả kích thước đều bằng pixel. Chuyển đổi qua LocalDensity.current: density.run { width.toDp() }.

Làm thế nào để đo văn bản trước khi tải phông chữ?

TextMeasurer sử dụng FontFamily.Default cho đến khi phông chữ tùy chỉnh được tải. Để đo chính xác, hãy chờ tải phông qua FontResource và sử dụng async/await.

TextMeasurer có ảnh hưởng đến hiệu suất danh sách LazyColumn không?

Đo văn bản trong mỗi mục LazyColumn có thể giảm FPS trên các thiết bị yếu. Tối ưu: lưu vùng đệm kết quả qua remember và giới hạn kích thước văn bản.

Tổng kết

  • TextMeasurer — API Compose để đo văn bản trước khi hiển thị, trả về TextLayoutResult.
  • Phương thức measure() chấp nhận văn bản, kiểu và Constraints bằng pixel.
  • Thể hiện được tạo qua rememberTextMeasurer() và tái sử dụng.
  • Cho văn bản một dòng, kết quả được sử dụng cho chiều rộng container thích ứng.
  • Cho văn bản nhiều dòng — lineCount, getLineWidth() và getBoundingBox() cung cấp bức tranh toàn cảnh.
  • Trong Layout tùy chỉnh, TextMeasurer cho phép căn chỉnh các phần tử theo dòng đầu tiên của văn bản.
  • Lưu vùng đệm kết quả qua remember và tránh đo trong các đường tái hợp thành nóng.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm