Compatibilidade com conteúdo escalonável pelo usuário

Implemente gestos de pinça para dar zoom para oferecer suporte a conteúdo escalonável no app. Esse é o método padrão e consistente com a plataforma para melhorar a acessibilidade, permitindo que os usuários ajustem de forma intuitiva o tamanho do texto e dos elementos da interface de acordo com as necessidades deles. Seu app pode definir um comportamento de dimensionamento personalizado com controle granular e comportamento contextual que oferece uma experiência que os usuários geralmente descobrem mais rapidamente do que um recurso no nível do sistema, como a ampliação da tela.

Escolher uma estratégia de escalonamento

As estratégias abordadas neste guia fazem com que a interface seja reorganizada para se ajustar à largura da tela. Isso oferece um benefício significativo de acessibilidade, eliminando a necessidade de movimentação horizontal e o movimento frustrante de "zigue-zague" que seria necessário para ler linhas longas de texto.

Leitura adicional: pesquisas confirmam que, para usuários com baixa visão, o conteúdo com refluxo é significativamente mais legível e fácil de navegar do que interfaces que exigem movimentação bidimensional. Para mais detalhes, consulte Uma comparação de conteúdo com formato adaptável e de panorâmica e digitalização em dispositivos móveis.

Dimensionar todos os elementos ou apenas os elementos do texto

A tabela a seguir demonstra o efeito visual de cada estratégia de dimensionamento.

Estratégia Escalonamento de densidade Ajuste da fonte

Comportamento

Redimensiona tudo proporcionalmente. O conteúdo é ajustado para caber no contêiner, então o usuário não precisa se mover horizontalmente para ver tudo.

Afeta apenas os elementos de texto. O layout geral e os componentes que não são de texto permanecem do mesmo tamanho.

O que é escalonado

Todos os elementos visuais: texto, componentes (botões, ícones), imagens e espaçamento de layout (preenchimento, margens)

Somente texto

Demonstração

Recomendações

Agora que você já conhece as diferenças visuais, a tabela a seguir ajuda a avaliar as vantagens e desvantagens e escolher a melhor estratégia para seu conteúdo.

Tipo de interface

Estratégia recomendada

Raciocínio

Layouts com foco na leitura

Exemplos: notícias, apps de mensagens

Densidade ou dimensionamento de fonte

O dimensionamento de densidade é preferível para dimensionar toda a área de conteúdo, incluindo imagens inline.

O dimensionamento de fonte é uma alternativa simples se apenas o texto precisar ser dimensionado.

Layouts visualmente estruturados

Exemplos: app stores, feeds de redes sociais

Escalonamento de densidade

Preserva as relações visuais entre imagens e texto em carrosséis ou grades. A natureza de refluxo evita a movimentação horizontal, que entraria em conflito com elementos de rolagem aninhados.

Detectar gestos de dimensionamento no Jetpack Compose

Para oferecer suporte a conteúdo escalonável pelo usuário, primeiro é necessário detectar gestos multitoque. No Jetpack Compose, isso pode ser feito usando o Modifier.transformable.

O modificador transformable é uma API de alto nível que fornece o delta zoomChange desde o último evento de gesto. Isso simplifica a lógica de atualização de estado para acumulação direta (por exemplo, scale *= zoomChange), tornando-a ideal para as estratégias de escalonamento adaptativo abordadas neste guia.

Exemplos de implementações

Os exemplos a seguir mostram como implementar as estratégias de dimensionamento de densidade e de fonte.

Escalonamento de densidade

Essa abordagem dimensiona o density base de uma área da interface. Como resultado, todas as medidas baseadas em layout, incluindo padding, espaçamento e tamanhos de componentes, são dimensionadas, como se o tamanho ou a resolução da tela tivessem mudado. Como o tamanho do texto também depende da densidade, ele também é dimensionado proporcionalmente. Essa estratégia é eficaz quando você quer ampliar de maneira uniforme todos os elementos em uma área específica, mantendo o ritmo visual geral e as proporções da interface.

private class DensityScalingState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledDensity(): Density {
        return Density(
            currentDensity.density * scaleFactor.floatValue,
            currentDensity.fontScale
        )
    }
}

@Composable
fun DensityScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState =
        remember(currentDensity) { DensityScalingState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledDensity()
        ) {
            DemoCard()
        }
    }
}

Ajuste da fonte

Essa estratégia é mais direcionada, modificando apenas o fator fontScale. O resultado é que apenas os elementos do texto aumentam ou diminuem, enquanto todos os outros componentes de layout, como contêineres, padding e ícones, permanecem com um tamanho fixo. Essa estratégia é adequada para melhorar a legibilidade do texto em apps com muita leitura.

class FontScaleState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledFont(): Density {
        return Density(
            currentDensity.density,
            currentDensity.fontScale * scaleFactor.floatValue
        )
    }
}

@Composable
fun FontScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState = remember { FontScaleState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledFont()
        ) {
            DemoCard()
        }
    }
}

Interface de demonstração compartilhada

Este é o elemento combinável DemoCard compartilhado usado pelos dois exemplos anteriores para destacar os diferentes comportamentos de dimensionamento.

@Composable
private fun DemoCard() {
    Card(
        modifier = Modifier
            .width(360.dp)
            .padding(16.dp),
        shape = RoundedCornerShape(12.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text("Demo Card", style = MaterialTheme.typography.headlineMedium)
            var isChecked by remember { mutableStateOf(true) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge)
                Switch(checked = isChecked, onCheckedChange = { isChecked = it })
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp))
                Spacer(Modifier.width(8.dp))
                Text("Demo Icon", style = MaterialTheme.typography.bodyLarge)
            }
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Blue)
                )
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Red)
                )
            }
            Text(
                "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
                    " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Justify
            )
        }
    }
}

Dicas e considerações

Para criar uma experiência mais refinada e acessível, considere as seguintes recomendações:

  • Ofereça controles de escala que não sejam por gestos: alguns usuários podem ter dificuldade com gestos. Para ajudar esses usuários, considere oferecer uma maneira alternativa de ajustar ou redefinir a escala que não dependa de gestos.
  • Crie para todas as escalas: teste sua interface com o dimensionamento no app e as configurações de fonte ou exibição do sistema. Verifique se os layouts do app se adaptam corretamente sem quebrar, sobrepor ou ocultar conteúdo. Saiba mais sobre como criar layouts adaptáveis.