Cómo brindar compatibilidad con diferentes tamaños de pantalla

La compatibilidad con diferentes tamaños de pantalla permite el acceso a tu app para todos los usuarios variedad de dispositivos y mayor cantidad de usuarios.

Para admitir tantos tamaños de pantalla como sea posible, diseña los diseños de tu app de modo que sean adaptable y adaptable. Los diseños responsivos o adaptables proporcionan un usuario optimizado sin importar el tamaño de la pantalla, lo que permite que tu app sea compatible con teléfonos, tablets, plegables, dispositivos ChromeOS, con orientación vertical y horizontal, y configuraciones redimensionables, por ejemplo, multiventana automático.

Los diseños responsivos o adaptables cambian en función del espacio de visualización disponible. Cambios van desde pequeños ajustes de diseño que ocupan espacio (diseño adaptable) hasta reemplazar por completo un diseño por otro para que tu app pueda adaptarse mejor diferentes tamaños de visualización (diseño adaptable).

Como kit de herramientas declarativas de IU, Jetpack Compose es ideal para diseñar y Implementación de diseños que cambian de forma dinámica para renderizar el contenido de forma diferente en una variedad de tamaños de visualización.

Realiza cambios en diseños grandes para elementos componibles explícitos a nivel de la pantalla

Cuando usas Compose para diseñar una aplicación completa, los elementos componibles a nivel de la app y a nivel de la pantalla ocupan todo el espacio que tiene tu app para renderizar. En este nivel de tu diseño, podría ser útil cambiar el diseño general de una pantalla para aprovechar las de mayor tamaño.

Evita usar valores físicos de hardware para tomar decisiones de diseño. Podría ser tentador tomar decisiones basadas en un valor fijo y tangible (¿el dispositivo es tablet? ¿la pantalla física tiene una relación de aspecto determinada?), pero las respuestas para estas preguntas puede no ser útil para determinar el espacio en el que puede funcionar la IU tus amigos.

Un diagrama en el que se muestran varios factores de forma de dispositivos diferentes, incluidos un teléfono, un dispositivo plegable, una tablet y una laptop.
Figura 1: Factores de forma de teléfonos, plegables, tablets y laptops

En las tablets, es posible que una app se ejecute en el modo multiventana, lo que significa que la app podría estar dividiendo la pantalla con otra app. En ChromeOS, es posible que una app esté en un una ventana que puede cambiar de tamaño. Incluso puede haber más de una pantalla física, como sucede con un dispositivo plegable. En todos estos casos, el tamaño físico de la pantalla no es relevante para decidir cómo mostrar el contenido.

En cambio, debes tomar decisiones según la parte real de la pantalla que se asigna a tu app, como las métricas de ventana actuales que proporciona la biblioteca WindowManager de Jetpack. Para ver cómo usar WindowManager en una app de Compose, consulta la muestra de JetNews.

Si sigues este enfoque, tu app podrá adaptarse con más facilidad, ya que se comportará correctamente en todas las situaciones anteriores. Cómo hacer que tus diseños se adapten al espacio de la pantalla disponibles para ellos también reduce la cantidad de manejo especial para respaldar plataformas, como ChromeOS, y factores de forma, como tablets y dispositivos plegables.

Una vez que observes el espacio relevante disponible para tu aplicación, será útil para convertir el tamaño sin procesar en una clase de tamaño significativo, como se describe en Ventana clases de tallas. Esta agrupa tamaños en buckets de tamaño estándar, que son puntos de interrupción diseñados para equilibrar la simplicidad con la flexibilidad para optimizar la app diferentes. Estas clases de tamaño hacen referencia a la ventana general de tu app, así que usa estas para decisiones de diseño que afecten el diseño general de tu pantalla. Puedes pasar estas clases de tamaño como un estado, o puedes realizar una lógica adicional crea un estado derivado para pasar a elementos componibles anidados.

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Este enfoque en capas limita la lógica del tamaño de la pantalla a una sola ubicación, en lugar de esparcirlo en tu app en muchos lugares que deben mantenerse sincronizados. Esta única ubicación produce un estado, que puede transmitirse explícitamente con otros elementos componibles, como lo harías con cualquier otro estado de la app. Este paso de estado de forma explícita simplifica los elementos componibles individuales, ya que serán funciones de componibilidad normales que toman la clase de tamaño o una configuración especificada junto con otros datos.

Los elementos componibles, flexibles y anidados son reutilizables

Los elementos componibles son más reutilizables cuando se pueden ubicar en varias ubicaciones. Si un elemento componible supone que siempre se colocará en un determinado ubicación con un tamaño específico, será más difícil reutilizarla en otra parte de un una ubicación diferente o una cantidad de espacio disponible diferente. Esto también significa que los elementos componibles individuales y reutilizables deben evitar depender implícitamente en "global" la información sobre el tamaño.

Considera el siguiente ejemplo: Imagina un elemento componible anidado que implementa un lista-detalles diseño, que puede mostrar uno o dos paneles, uno al lado del otro.

Captura de pantalla de una app que muestra dos paneles, uno al lado del otro.
Figura 2: Captura de pantalla de una app que muestra un diseño típico de lista-detalles (1 es el área de lista). 2, el área de detalles.

Queremos que esta decisión forme parte del diseño general de la app, por lo que la pasamos desde un elemento componible a nivel de la pantalla, como vimos con anterioridad:

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

¿Y si, en cambio, queremos que un elemento componible cambie su diseño de forma independiente en función de el espacio disponible? Por ejemplo, una tarjeta que quiere mostrar detalles adicionales si el espacio lo permite. Queremos realizar alguna lógica según el tamaño disponible, pero específicamente, ¿en qué tamaño?

Ejemplos de dos tarjetas diferentes.
Figura 3: Tarjeta estrecha que muestra solo un ícono y un título, y una más amplia con el ícono, el título y la descripción breve.

Como vimos anteriormente, debemos evitar usar el tamaño real en la pantalla. Esto no será preciso en varias pantallas ni será preciso si la app no está en pantalla completa.

Dado que el elemento componible no es un elemento de este tipo a nivel de la pantalla, tampoco debemos usar las métricas de ventana actuales directamente para maximizar la reutilización. Si el componente se coloca con padding (como las inserciones), o bien si hay componentes como rieles de navegación o barras de la app, la cantidad de espacio disponible para el elemento componible puede diferir de forma significativa del espacio general que está disponible para la app.

Por lo tanto, debemos usar el ancho por el que el elemento componible realmente se renderiza. Existen dos opciones para obtener ese ancho:

Si quieres cambiar dónde o cómo se muestra el contenido, puedes usar una una colección de modificadores o un diseño personalizado para que el diseño sea adaptable. Esto podría ser tan simple como que algunos elementos secundarios llenen todo el espacio disponible o colocar elementos secundarios con varias columnas si hay suficiente espacio.

Si deseas cambiar qué es lo que muestras, puedes usar BoxWithConstraints como una alternativa más potente. Este elemento componible proporciona medición restricciones que puedes usar para llamar a diferentes elementos componibles según el espacio que disponibles. Sin embargo, esto tiene algunos gastos, ya que BoxWithConstraints aplaza la composición hasta la fase de diseño, cuando se conocen estas restricciones, lo que provoca más trabajo durante el diseño.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

Garantiza la disponibilidad de datos para diferentes tamaños

Cuando aproveches el espacio adicional de la pantalla, es posible que, en una de mayor tamaño, tengas más lugar disponible para mostrar más contenido al usuario que en una pequeña. Cuando implementas un elemento componible con este comportamiento, puede ser tentador ser eficiente y cargar los datos como un efecto secundario del tamaño actual.

Sin embargo, esto va en contra de los principios del flujo unidireccional de datos, en el que los datos se pueden elevar y proporcionar a elementos componibles para que se rendericen de forma adecuada. Suficiente se deben proporcionar los datos al elemento componible para que este siempre tenga debe mostrarse en cualquier tamaño, aunque parte de los datos no siempre debe usarse.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Si compilas a partir del ejemplo Card, ten en cuenta que siempre pasamos el elemento description al Card. Aunque description solo se usa cuando el ancho permite que se muestre, Card siempre lo requiere, sin importar el ancho disponible.

Pasar datos siempre simplifica los diseños adaptables haciéndolos menos y evita que se desencadenen efectos secundarios al cambiar de tamaños (lo que puede ocurrir debido a un cambio de tamaño de ventana, un cambio de orientación o un plegado o desplegado de un dispositivo).

Este principio también permite preservar el estado en cambios de diseño. Mediante la elevación información que puede no usarse en todos los tamaños, podemos preservar el estado del usuario a medida que cambia el tamaño del diseño. Por ejemplo, podemos elevar una marca booleana showMore. para que se conserve el estado del usuario cuando los cambios de tamaño hagan que el diseño cambie entre ocultar y mostrar la descripción:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

Más información

Si deseas obtener más información sobre diseños personalizados en Compose, consulta los siguientes recursos adicionales.

Apps de ejemplo

  • Diseños canónicos de pantalla grande es un repositorio de patrones de diseño comprobados que proporcionan una experiencia en dispositivos con pantallas grandes
  • JetNews se muestra cómo diseñar una app que adapte su IU para aprovechar el espacio disponible
  • Responder es un ejemplo adaptable para brindar compatibilidad con dispositivos móviles, tablets y plegables
  • Now in Android es una App que usa diseños adaptables para admitir diferentes tamaños de pantalla

Videos