Admite contenido escalable para el usuario

Implementa gestos de pellizcar para hacer zoom para admitir contenido escalable en tu app. Este es el método estándar y coherente con la plataforma para mejorar la accesibilidad, ya que permite que los usuarios ajusten de forma intuitiva el tamaño del texto y los elementos de la IU para que se adapten a sus necesidades. Tu app puede definir un comportamiento de escalamiento personalizado con control detallado y un comportamiento contextual que ofrezca una experiencia que los usuarios suelen descubrir con mayor rapidez que una función a nivel del sistema, como la ampliación de la pantalla.

Elige una estrategia de escalamiento

Las estrategias que se describen en esta guía hacen que la IU se reorganice y se adapte al ancho de la pantalla. Esto proporciona un beneficio de accesibilidad significativo, ya que elimina la necesidad de desplazarse de forma horizontal y el frustrante movimiento de "zigzag" que, de lo contrario, sería necesario para leer líneas de texto largas.

Lectura adicional: Las investigaciones confirman que, para los usuarios con visión reducida, el contenido con ajuste dinámico es mucho más legible y fácil de navegar que las interfaces que requieren un desplazamiento bidimensional. Para obtener más detalles, consulta Comparación entre el contenido de desplazamiento lateral y el adaptable en dispositivos móviles.

Ajusta la escala de todos los elementos o solo de los elementos de texto

En la siguiente tabla, se muestra el efecto visual de cada estrategia de escalamiento.

Estrategia Escalamiento de densidad Ajuste de la fuente

Behavior

Escala todo proporcionalmente. El contenido se redistribuye para ajustarse a su contenedor, por lo que el usuario no necesita desplazarse horizontalmente para ver todo el contenido.

Solo afecta a los elementos de texto. El diseño general y los componentes que no son de texto mantienen el mismo tamaño.

Qué se escala

Todos los elementos visuales: Texto, componentes (botones, íconos), imágenes y espaciado del diseño (relleno, márgenes)

Solo texto

Demostración

Recomendaciones

Ahora que viste las diferencias visuales, la siguiente tabla te ayudará a sopesar las ventajas y desventajas, y a elegir la mejor estrategia para tu contenido.

Tipo de IU

Estrategia recomendada

Motivo

Diseños con mucho texto

Ejemplos: artículos de noticias, apps de mensajería

Densidad o ajuste de escala de la fuente

Se prefiere el escalamiento de densidad para escalar toda el área de contenido, incluidas las imágenes intercaladas.

El ajuste de escala de la fuente es una alternativa sencilla si solo se debe ajustar la escala del texto.

Diseños estructurados visualmente

Ejemplos: Tiendas de aplicaciones, feeds de redes sociales

Ajuste de densidad

Conserva las relaciones visuales entre las imágenes y el texto en carruseles o cuadrículas. La naturaleza de reflujo evita el desplazamiento horizontal, que entraría en conflicto con los elementos de desplazamiento anidados.

Cómo detectar gestos de escalamiento en Jetpack Compose

Para admitir contenido escalable por el usuario, primero debes detectar los gestos de varios toques. En Jetpack Compose, puedes hacerlo con Modifier.transformable.

El modificador transformable es una API de alto nivel que proporciona el delta zoomChange desde el último evento de gesto. Esto simplifica la lógica de actualización de estado a la acumulación directa (por ejemplo, scale *= zoomChange), lo que la hace ideal para las estrategias de escalamiento adaptable que se describen en esta guía.

Implementaciones de ejemplo

En los siguientes ejemplos, se muestra cómo implementar las estrategias de escalamiento de densidad y de fuentes.

Escalamiento de densidad

Este enfoque escala el density base de un área de la IU. Como resultado, se ajustan todas las mediciones basadas en el diseño, incluidos el padding, el espaciado y los tamaños de los componentes, como si hubiera cambiado el tamaño o la resolución de la pantalla. Como el tamaño del texto también depende de la densidad, también se escala de forma proporcional. Esta estrategia es eficaz cuando quieres ampliar de manera uniforme todos los elementos dentro de un área específica, manteniendo el ritmo visual general y las proporciones de tu IU.

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 de la fuente

Esta estrategia es más específica, ya que solo modifica el factor fontScale. El resultado es que solo los elementos de texto aumentan o disminuyen, mientras que todos los demás componentes del diseño, como los contenedores, el padding y los íconos, permanecen con un tamaño fijo. Esta estrategia es adecuada para mejorar la legibilidad del texto en apps con mucha lectura.

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

IU de demostración compartida

Este es el elemento DemoCard componible compartido que usan los dos ejemplos anteriores para destacar los diferentes comportamientos de escalamiento.

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

Sugerencias y consideraciones

Para crear una experiencia más refinada y accesible, ten en cuenta las siguientes recomendaciones:

  • Considera ofrecer controles de escala que no sean gestos: Algunos usuarios pueden tener dificultades con los gestos. Para ayudar a estos usuarios, considera proporcionar una forma alternativa de ajustar o restablecer la escala que no dependa de los gestos.
  • Compila para todas las escalas: Prueba tu IU con el escalamiento integrado en la app y la configuración de fuente o pantalla de todo el sistema. Verifica que los diseños de tu app se adapten correctamente sin romper, superponer ni ocultar contenido. Obtén más información para compilar diseños adaptables.