TextMeasurer: what it is and how text measurement works in Compose

Author: IT Sectr Published: 2026-07-25 Reading time: 8 min

TextMeasurer is an API from Jetpack Compose designed for measuring text dimensions before it is actually rendered on screen. Unlike the classic approach using Paint.measureText in Android View, TextMeasurer provides a Compose-native way to obtain width, height, and line metrics while taking style and constraints into account. According to Android Developers Documentation (2025), TextMeasurer is actively used in custom Layout components, animated labels, and adaptive UI elements where text size affects the positioning of other elements.

Key Takeaways

  • TextMeasurer — A Compose API for measuring text before rendering.
  • The measure() method returns TextLayoutResult with line and character metrics.
  • The API accounts for style, font, size, and container constraints in pixels.
  • TextMeasurer supports measuring single-line and multi-line text.
  • The measurement result is used for custom layout and text animation.

What is TextMeasurer in Jetpack Compose?

TextMeasurer is a functional interface from the androidx.compose.ui.text package that allows measuring text in Compose without actually rendering it on screen. It is part of the Compose text engine and operates at the Paragraph API level, which uses Skia for rendering.

The main purpose of TextMeasurer is pre-measurement. In Compose, text size is usually determined after the Composable is already placed in the Layout. However, there are scenarios where you need to know the text width before layout — for example, to decide whether a string fits in a container, or to calculate a button size based on text length.

TextMeasurer solves this by providing the measure() method, which takes a TextMeasureRequest (text, style, constraints) and returns TextLayoutResult (metrics, lines, characters). This allows the developer to obtain all text data synchronously, without a one-frame lag.

Use TextMeasurer when text size affects the parent component size, when creating animated text transitions, or when implementing custom Layouts where text coexists with other elements.

How TextMeasurer Works

The TextMeasurer API consists of two key entities: rememberTextMeasurer() (creating an instance) and measure() (running the measurement). The instance is created once for the entire component lifecycle and is reused for all measurements.

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

The constraints parameter defines the maximum width and height within which the text should fit. If the text does not fit within the width, it wraps to the next line. The measurement result contains a TextLayoutResult, which provides the number of lines, the position of each character, the width of each line, and the overall text size.

TextMeasurer works synchronously on the UI thread and does not trigger recomposition. However, frequent text measurement with different styles can create overhead — so the instance is created via remember and reused.

Single-Line Text Measurement

The simplest scenario is measuring the width of single-line text to determine the parent container size. For example, for a button whose width depends on the label length.

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

In this example, remember(label) ensures that the measurement restarts only when the text changes. Constraints are set with a large maxWidth (1000) to prevent line wrapping — the actual constraint will equal the text width. The Box width is set to the measured text width, resulting in a “button sized to text” effect.

Note: the width from the result needs to be converted to dp using .dp, since TextMeasurer returns dimensions in pixels, while Modifier.width expects Dp. For accuracy, use density and LocalDensity.current.

Multi-Line Text Measurement

For multi-line text, TextMeasurer allows you to determine not only the total height but also the number of lines and the position of each character within the paragraph. This is necessary when creating custom text fields, chats, or editors.

The main TextLayoutResult properties for multi-line text: lineCount (number of lines), getLineTop(index) (Y-coordinate of line start), getLineBottom(index) (Y-coordinate of line end), getLineWidth(index) (line width). By combining these, you can precisely position interface elements relative to text lines.

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

The getLineWidth(lineIndex) method returns the width of the specified line in pixels. This may be less than maxWidth if the line does not reach the container boundary. Knowing the width of each line, you can implement a justify effect manually or place inline elements next to short lines.

To get the position of a specific character, use getBoundingBox(offset), which returns a Rect with left, top, right, bottom coordinates. This is useful for cursor positioning in custom text fields or for highlighting a text range with animation.

TextMeasurer in Custom Components

The most powerful application of TextMeasurer is custom Layout components, where text coexists with other elements and their sizes are interdependent. For example, an “Icon + text” widget where the icon should be centered relative to the first line of text, not the entire block.

In a standard Row, the icon is centered by the entire Row height, which looks unnatural when the text takes up 3 lines while the icon is just one. With TextMeasurer, you can measure the first line and align the icon precisely to it.

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

In this example, measuring the first line via TextMeasurer determines the icon position. Without TextMeasurer, the icon would be centered by the entire height of the multi-line text, which is visually incorrect. This pattern is widely used in chats, contact lists, and info cards.

Performance Tips

TextMeasurer is a synchronous API running on the UI thread. Overusing it can cause frame drops, especially when measuring large text volumes on weak devices.

The first rule — cache the result. Always wrap the measure() call in remember with text and style dependencies. Never call measure() inside CompositionLocal or in hot recomposition paths.

The second rule — limit maxWidth and maxHeight. If you set maxWidth = Constraints.Infinity, TextMeasurer will not be able to wrap lines and the text will go off-screen. Always pass realistic constraints, especially for multi-line text.

The third rule — avoid repeated measurement with the same parameters. If the text and style have not changed, the measure() result will be the same. Combine measurement with Deferred or LaunchedEffect for async scenarios.

Frequently Asked Questions

How is TextMeasurer different from onSizeChanged in Compose?

TextMeasurer measures text before rendering, while onSizeChanged measures after. TextMeasurer is needed when text size affects the parent layout; onSizeChanged works for reactive logic after composition.

Can SpannableString be measured through TextMeasurer?

Yes, TextMeasurer.measure() accepts AnnotatedString, which includes SpannableString with inline styles, links, and colors. All spans are accounted for during measurement.

Why does TextMeasurer return dimensions in pixels instead of dp?

TextMeasurer operates at the Skia rendering level, where all dimensions are in pixels. Convert via LocalDensity.current: density.run { width.toDp() }.

How to measure text before a font is loaded?

TextMeasurer uses FontFamily.Default until a custom font is loaded. For accurate measurement, wait for the font to load via FontResource and use async/await.

Does TextMeasurer affect LazyColumn list performance?

Measuring text in each LazyColumn item can reduce FPS on weak devices. Optimization: cache the result via remember and limit the text size.

Summary

  • TextMeasurer — A Compose API for measuring text before rendering, returns TextLayoutResult.
  • The measure() method accepts text, style, and Constraints in pixels.
  • The instance is created via rememberTextMeasurer() and reused.
  • For single-line text, the result is used for adaptive container width.
  • For multi-line text — lineCount, getLineWidth(), and getBoundingBox() provide the full picture.
  • In custom Layouts, TextMeasurer allows aligning elements by the first text line.
  • Cache the result via remember and avoid measuring in hot recomposition paths.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also