TextMeasurer é uma API do Jetpack Compose projetada para medir as dimensões do texto antes de sua renderização real na tela. Diferente da abordagem clássica via Paint.measureText no Android View, o TextMeasurer fornece uma forma nativa do Compose de obter largura, altura e métricas de linhas, levando em conta o estilo e as restrições. De acordo com a Android Developers Documentation (2025), o TextMeasurer é ativamente usado em componentes Layout personalizados, rótulos animados e elementos de UI adaptativos onde o tamanho do texto afeta o posicionamento de outros elementos.
Pontos Principais
TextMeasurer é uma interface funcional do pacote androidx.compose.ui.text que permite medir texto no Compose sem realmente renderizá-lo na tela. Faz parte do motor de texto do Compose e opera no nível da Paragraph API, que usa Skia para renderização.
O principal propósito do TextMeasurer é a pré-medição. No Compose, o tamanho do texto geralmente é determinado depois que o Composable já está colocado no Layout. No entanto, existem cenários onde é preciso saber a largura do texto antes da composição — por exemplo, para decidir se uma string cabe em um contêiner, ou para calcular o tamanho de um botão com base no comprimento do texto.
TextMeasurer resolve isso fornecendo o método measure(), que recebe um TextMeasureRequest (texto, estilo, restrições) e retorna TextLayoutResult (métricas, linhas, caracteres). Isso permite que o desenvolvedor obtenha todos os dados do texto de forma síncrona, sem atraso de um quadro.
Use TextMeasurer quando o tamanho do texto afetar o tamanho do componente pai, ao criar transições de texto animadas ou ao implementar Layouts personalizados onde o texto coexiste com outros elementos.
A API TextMeasurer consiste em duas entidades principais: rememberTextMeasurer() (criação de instância) e measure() (execução da medição). A instância é criada uma vez para todo o ciclo de vida do componente e reutilizada para todas as medições.
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
O parâmetro constraints define a largura e altura máximas dentro das quais o texto deve caber. Se o texto não couber na largura, ele quebra para a próxima linha. O resultado da medição contém um TextLayoutResult, que fornece o número de linhas, a posição de cada caractere, a largura de cada linha e o tamanho total do texto.
TextMeasurer funciona de forma síncrona na thread de UI e não desencadeia recomposição. No entanto, a medição frequente de texto com estilos diferentes pode criar sobrecarga — portanto, a instância é criada via remember e reutilizada.
O cenário mais simples é medir a largura de texto de linha única para determinar o tamanho do contêiner pai. Por exemplo, para um botão cuja largura depende do comprimento do rótulo.
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)
}
Neste exemplo, remember(label) garante que a medição reinicie apenas quando o texto mudar. As restrições são definidas com um maxWidth grande (1000) para evitar quebra de linha — a restrição real será igual à largura do texto. A largura do Box é definida como a largura medida do texto, resultando em um efeito de “botão ajustado ao texto”.
Nota: a largura do resultado precisa ser convertida para dp usando .dp, já que TextMeasurer retorna dimensões em pixels, enquanto Modifier.width espera Dp. Para precisão, use density e LocalDensity.current.
Para texto multilinha, o TextMeasurer permite determinar não apenas a altura total, mas também o número de linhas e a posição de cada caractere dentro do parágrafo. Isso é necessário ao criar campos de texto personalizados, chats ou editores.
As principais propriedades do TextLayoutResult para texto multilinha: lineCount (número de linhas), getLineTop(index) (coordenada Y do início da linha), getLineBottom(index) (coordenada Y do final da linha), getLineWidth(index) (largura da linha). Combinando-as, você pode posicionar com precisão elementos de interface em relação às linhas de texto.
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
O método getLineWidth(lineIndex) retorna a largura da linha especificada em pixels. Pode ser menor que maxWidth se a linha não atingir o limite do contêiner. Conhecendo a largura de cada linha, você pode implementar um efeito de justificação manualmente ou colocar elementos inline ao lado de linhas curtas.
Para obter a posição de um caractere específico, use getBoundingBox(offset), que retorna um Rect com coordenadas left, top, right, bottom. Isso é útil para posicionar o cursor em campos de texto personalizados ou para destacar um intervalo de texto com animação.
A aplicação mais poderosa do TextMeasurer são os componentes Layout personalizados, onde o texto coexiste com outros elementos e seus tamanhos são interdependentes. Por exemplo, um widget “Ícone + texto” onde o ícone deve ser centralizado em relação à primeira linha de texto, não ao bloco inteiro.
Em um Row padrão, o ícone é centralizado por toda a altura do Row, o que parece pouco natural quando o texto ocupa 3 linhas e o ícone apenas uma. Com TextMeasurer, você pode medir a primeira linha e alinhar o ícone precisamente a ela.
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)
}
}
)
Neste exemplo, medir a primeira linha via TextMeasurer determina a posição do ícone. Sem TextMeasurer, o ícone seria centralizado por toda a altura do texto multilinha, o que é visualmente incorreto. Este padrão é amplamente usado em chats, listas de contatos e cartões de informação.
TextMeasurer é uma API síncrona executada na thread de UI. O uso excessivo pode causar queda de quadros, especialmente ao medir grandes volumes de texto em dispositivos fracos.
Primeira regra — armazene o resultado em cache. Sempre envolva a chamada measure() em remember com dependências de texto e estilo. Nunca chame measure() dentro de CompositionLocal ou em caminhos de recomposição quentes.
Segunda regra — limite maxWidth e maxHeight. Se você definir maxWidth = Constraints.Infinity, o TextMeasurer não conseguirá quebrar linhas e o texto sairá da tela. Sempre passe restrições realistas, especialmente para texto multilinha.
Terceira regra — evite medição repetida com os mesmos parâmetros. Se o texto e o estilo não mudaram, o resultado de measure() será o mesmo. Combine medição com Deferred ou LaunchedEffect para cenários assíncronos.
Perguntas Frequentes
TextMeasurer mede o texto antes da renderização, enquanto onSizeChanged mede depois. TextMeasurer é necessário quando o tamanho do texto afeta o layout pai; onSizeChanged funciona para lógica reativa após a composição.
Sim, TextMeasurer.measure() aceita AnnotatedString, que inclui SpannableString com estilos inline, links e cores. Todos os spans são considerados durante a medição.
TextMeasurer opera no nível de renderização Skia, onde todas as dimensões estão em pixels. Converta via LocalDensity.current: density.run { width.toDp() }.
TextMeasurer usa FontFamily.Default até que uma fonte personalizada seja carregada. Para medição precisa, aguarde o carregamento da fonte via FontResource e use async/await.
Medir texto em cada item do LazyColumn pode reduzir FPS em dispositivos fracos. Otimização: armazene em cache o resultado via remember e limite o tamanho do texto.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também