1. Introducción
En este codelab, aprenderás a compilar apps adaptables para teléfonos, tablets y plegables, y cómo estas mejoran la accesibilidad con Jetpack Compose. También conocerás las prácticas recomendadas para usar temas y componentes de Material 3.
Antes de comenzar, es importante comprender qué entendemos por adaptabilidad.
Adaptabilidad
La IU de tu app debe ser responsiva para los diferentes tamaños de ventana, orientaciones y factores de forma. Un diseño adaptable cambia en función del espacio de pantalla disponible. Estos cambios varían desde simples ajustes de diseño para llenar espacio, con la elección de los estilos de navegación respectivos, hasta cambios completos de diseño para aprovechar espacio adicional.
Para obtener más información, consulta Diseño adaptable.
En este codelab, explorarás cómo usar la adaptabilidad y pensar en ella cuando uses Jetpack Compose. Compilarás una aplicación, llamada Reply, que te mostrará cómo implementar la adaptabilidad para todo tipo de pantallas, y cómo la adaptabilidad y la accesibilidad funcionan en conjunto para brindar a los usuarios una experiencia óptima.
Qué aprenderás
- Cómo diseñar tu app para que se oriente a todos los tamaños de ventana con Jetpack Compose
- Cómo segmentar tu app para diferentes dispositivos plegables
- Cómo usar diferentes tipos de navegación para lograr una mejor accesibilidad
- Cómo usar los componentes de Material 3 para proporcionar la mejor experiencia para cada tamaño de ventana
Requisitos
- La versión estable más reciente de Android Studio
- Un dispositivo virtual de Android 13 que puede cambiar de tamaño.
- Conocimientos sobre Kotlin
- Conocimientos básicos sobre Compose (como la anotación
@Composable
) - Conocimientos básicos sobre diseños de Compose (p. ej.,
Row
yColumn
) - Conocimientos básicos sobre modificadores (p. ej.,
Modifier.padding()
)
En este codelab, usarás el emulador de tamaño variable, que te permite alternar entre diferentes tipos de dispositivos y tamaños de ventana.
Si no estás familiarizado con Compose, antes de completar este codelab, considera realizar el codelab de los principios básicos de Jetpack Compose.
Qué compilarás
- Una app cliente de correo electrónico interactiva llamada Reply, en la que se usan prácticas recomendadas para diseños adaptables, diferentes navegaciones de Material y un uso óptimo del espacio de pantalla.
2. Prepárate
Para obtener el código necesario para este codelab, clona el repositorio de GitHub desde la línea de comandos:
git clone https://github.com/android/codelab-android-compose.git cd codelab-android-compose/AdaptiveUiCodelab
También tienes la opción de descargar el repositorio como archivo ZIP:
Te recomendamos que comiences con el código de la rama main y sigas el codelab paso a paso a tu propio ritmo.
Abre el proyecto en Android Studio
- En la ventana Welcome to Android Studio, selecciona
Open an Existing Project.
- Selecciona la carpeta
<Download Location>/AdaptiveUiCodelab
(asegúrate de seleccionar el directorioAdaptiveUiCodelab
que contienebuild.gradle
). - Cuando Android Studio haya importado el proyecto, prueba si puedes ejecutar la rama
main
.
Cómo explorar el código de inicio
El código de la rama main contiene el paquete ui
. Trabajarás con los siguientes archivos en ese paquete:
MainActivity.kt
: Es la actividad de punto de entrada donde inicias tu app.ReplyApp.kt
: Contiene elementos componibles de la IU de la pantalla principal.ReplyHomeViewModel.kt
: Proporciona los datos y el estado de la IU para el contenido de la app.ReplyListContent.kt
: Contiene elementos componibles para proporcionar pantallas de detalles y listas.
Si ejecutas esta app en un emulador de tamaño variable y pruebas diferentes tipos de dispositivos, como un teléfono o una tablet, la IU simplemente se expande al espacio determinado en lugar de aprovechar el espacio de pantalla o proporcionar una ergonomía de accesibilidad.
Lo actualizarás para aprovechar el espacio de la pantalla, aumentar la usabilidad y mejorar la experiencia general del usuario.
3. Cómo lograr que las apps sean adaptables
En esta sección, se explica qué significa hacer que las apps sean adaptables y qué componentes proporciona Material 3 para hacerlo más fácil. También abarca los tipos de pantallas y estados a los que te orientarás, incluidos teléfonos, tablets, tablets grandes y plegables.
Empezarás repasando los aspectos básicos de los tamaños de ventana, las posiciones de plegado y los diferentes tipos de opciones de navegación. Luego, puedes usar estas APIs en tu app para que se adapte mejor.
Tamaños de las ventanas
Los dispositivos Android pueden tener diferentes formas y tamaños, desde teléfonos y plegables hasta tablets y dispositivos ChromeOS. Para admitir tantos tamaños de ventana como sea posible, tu IU debe ser responsiva y adaptable. Para ayudarte a encontrar el umbral correcto en el que se debe cambiar la IU de tu app, definimos valores de puntos de interrupción que ayudan a clasificar los dispositivos en clases de tamaño predefinidas (compactas, medianas y expandidas), llamadas clases de tamaño de ventana. Estos son un conjunto de puntos de interrupción de viewports bien definidos que te ayudan a diseñar, desarrollar y probar diseños de aplicaciones responsivos y adaptables.
Las categorías se eligieron específicamente a los efectos de equilibrar la simplicidad del diseño, con la flexibilidad para optimizar tu app en casos únicos. El espacio de pantalla disponible para la app siempre determina la clase de tamaño de ventana, que puede no ser la pantalla física completa debido a la realización de varias tareas a la vez o a otras segmentaciones.
El ancho y la altura se clasifican por separado, por lo que en cualquier momento, tu app tiene dos clases de tamaño de ventana: una para el ancho y otra para la altura. El ancho disponible suele ser más importante que la altura disponible debido a la ubicuidad del desplazamiento vertical, por lo que, en este caso, también usarás clases de tamaño de ancho.
Estados de plegado
Los dispositivos plegables presentan aún más situaciones a las que tu app puede adaptarse debido a sus diferentes tamaños y a la presencia de bisagras. Las bisagras pueden oscurecer parte de la pantalla, lo que hace que el área no sea adecuada para mostrar contenido. también podrían estar separando, lo que significa que hay dos pantallas físicas separadas cuando el dispositivo está desplegado.
Además, el usuario podría estar mirando la pantalla interior mientras la bisagra está parcialmente abierta, lo que daría como resultado distintas posiciones físicas según la orientación del pliegue: la posición en la mesa (pliegue horizontal, que se muestra a la derecha en la imagen de arriba) y la posición de libro (pliegue vertical).
Obtén más información sobre las posturas y bisagras de plegado.
Todas estas opciones son aspectos que se deben tener en cuenta a la hora de implementar diseños adaptables compatibles con dispositivos plegables.
Obtén información adaptable
La biblioteca de adaptive
de Material3 proporciona acceso conveniente a la información sobre la ventana en la que se ejecuta tu app.
- Agrega entradas para este artefacto y su versión al archivo del catálogo de versiones:
gradle/libs.versions.toml
[versions]
material3Adaptive = "1.0.0-beta01"
[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
- En el archivo de compilación del módulo de la app, agrega la nueva dependencia de la biblioteca y, luego, realiza una sincronización de Gradle:
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive)
}
Ahora, en cualquier alcance componible, puedes usar currentWindowAdaptiveInfo()
para obtener un objeto WindowAdaptiveInfo
que contenga información como la clase de tamaño de ventana actual y si el dispositivo está en una posición plegable, como la de mesa.
Puedes probarlo ahora en MainActivity
.
- En
onCreate()
, dentro del bloqueReplyTheme
, obtén la información adaptable de la ventana y muestra las clases de tamaño en un elementoText
componible. Puedes agregar lo siguiente después del elementoReplyApp()
:
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReplyTheme {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
ReplyApp(
replyHomeUIState = uiState,
onEmailClick = viewModel::setSelectedEmail
)
val adaptiveInfo = currentWindowAdaptiveInfo()
val sizeClassText =
"${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
"${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
Text(
text = sizeClassText,
color = Color.Magenta,
modifier = Modifier.padding(
WindowInsets.safeDrawing.asPaddingValues()
)
)
}
}
}
Cuando se ejecute la app, se mostrarán las clases de tamaño de ventana impresas en el contenido de la app. Siéntete libre de explorar qué más se proporciona en la información adaptable de la ventana. Luego, podrás quitar este Text
, ya que cubre el contenido de la app y no será necesario para los pasos siguientes.
4. Navegación dinámica
Ahora, adaptarás la navegación de la app a medida que cambie el estado y el tamaño del dispositivo, de modo que sea más fácil de usar.
Cuando los usuarios sostienen un teléfono, sus dedos suelen estar en la parte inferior de la pantalla. Cuando los usuarios sostienen un dispositivo plegable o una tablet abiertos, suelen tener los dedos cerca de los lados. Tus usuarios deben poder navegar o iniciar una interacción con una app sin requerir posiciones extremas de las manos ni cambiar la ubicación.
Cuando diseñes tu app y decidas dónde colocar los elementos interactivos de la IU en el diseño, ten en cuenta las implicaciones ergonómicas de las diferentes regiones de la pantalla.
- ¿Qué áreas son cómodas de alcanzar mientras se sostiene el dispositivo?
- ¿A qué áreas se puede llegar solo extendiendo los dedos, lo que puede resultar molesto?
- ¿Qué áreas son difíciles de alcanzar o están lejos del lugar donde el usuario sostiene el dispositivo?
La navegación es lo primero con lo que interactúan los usuarios y contiene acciones de gran importancia relacionadas con los recorridos críticos del usuario, por lo que debe colocarse en las áreas de más fácil acceso. La biblioteca adaptable de Material proporciona varios componentes que te ayudan a implementar la navegación, según la clase de tamaño de ventana del dispositivo.
Navegación inferior
La navegación inferior es perfecta para tamaños compactos, ya que naturalmente sostenemos el dispositivo de modo que nuestro dedo pulgar pueda llegar con facilidad a todos los puntos táctiles de navegación inferior. Úsala siempre que tengas un tamaño de dispositivo compacto o plegable en ese estado.
Riel de navegación
Para un tamaño de ventana mediano, el riel de navegación es ideal para la accesibilidad, ya que nuestro dedo pulgar cae naturalmente a un lado del dispositivo. También puedes combinar un riel de navegación con un panel lateral de navegación para mostrar más información.
Panel lateral de navegación
El panel lateral de navegación proporciona una forma fácil de ver información detallada de las pestañas de navegación, y es fácil de acceder cuando usas tablets o dispositivos más grandes. Hay dos tipos de paneles laterales de navegación disponibles: uno modal y uno permanente.
Panel lateral de navegación modal
Puedes usar un panel lateral de navegación modal para teléfonos y tablets de tamaño compacto a mediano, ya que se puede ocultar o expandir como una superposición en el contenido. A veces, se puede combinar con un riel de navegación.
Panel lateral de navegación permanente
Puedes usar un panel lateral de navegación permanente para la navegación fija en tablets, Chromebooks y computadoras de escritorio grandes.
Cómo implementar la navegación dinámica
Ahora, cambiarás entre los diferentes tipos de navegación a medida que cambien el estado y el tamaño del dispositivo.
Actualmente, la app siempre muestra un NavigationBar
debajo del contenido de la pantalla, independientemente del estado del dispositivo. En su lugar, puedes usar el componente NavigationSuiteScaffold
de Material para cambiar automáticamente entre los diferentes componentes de navegación según información como la clase de tamaño de ventana actual.
- Agrega la dependencia de Gradle para obtener este componente actualizando el catálogo de versiones y la secuencia de comandos de compilación de la app. Luego, realiza una sincronización de Gradle:
gradle/libs.versions.toml
[versions]
material3AdaptiveNavSuite = "1.3.0-beta01"
[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive.navigation.suite)
}
- Busca la función de componibilidad
ReplyNavigationWrapper()
enReplyApp.kt
y reemplazaColumn
y su contenido porNavigationSuiteScaffold
:
ReplyApp.kt
@Composable
private fun ReplyNavigationWrapperUI(
content: @Composable () -> Unit = {}
) {
var selectedDestination: ReplyDestination by remember {
mutableStateOf(ReplyDestination.Inbox)
}
NavigationSuiteScaffold(
navigationSuiteItems = {
ReplyDestination.entries.forEach {
item(
selected = it == selectedDestination,
onClick = { /*TODO update selection*/ },
icon = {
Icon(
imageVector = it.icon,
contentDescription = stringResource(it.labelRes)
)
},
label = {
Text(text = stringResource(it.labelRes))
},
)
}
}
) {
content()
}
}
El argumento navigationSuiteItems
es un bloque que te permite agregar elementos con la función item()
, similar a agregar elementos en un LazyColumn
. Dentro de la expresión lambda final, este código llama al elemento content()
que se pasa como argumento a ReplyNavigationWrapperUI()
.
Ejecuta la app en el emulador e intenta cambiar los tamaños entre teléfono, plegable y tablet. Verás que la barra de navegación cambia a un riel de navegación y viceversa.
En ventanas muy anchas, como en una tablet en orientación horizontal, es posible que quieras mostrar el panel lateral de navegación permanente. NavigationSuiteScaffold
admite mostrar un panel lateral permanente, aunque no se muestra en ninguno de los valores actuales de WindowWidthSizeClass
. Sin embargo, puedes hacerlo con un pequeño cambio.
- Agrega el siguiente código justo antes de la llamada a
NavigationSuiteScaffold
:
ReplyApp.kt
@Composable
private fun ReplyNavigationWrapperUI(
content: @Composable () -> Unit = {}
) {
var selectedDestination: ReplyDestination by remember {
mutableStateOf(ReplyDestination.Inbox)
}
val windowSize = with(LocalDensity.current) {
currentWindowSize().toSize().toDpSize()
}
val layoutType = if (windowSize.width >= 1200.dp) {
NavigationSuiteType.NavigationDrawer
} else {
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
currentWindowAdaptiveInfo()
)
}
NavigationSuiteScaffold(
layoutType = layoutType,
...
) {
content()
}
}
Este código primero obtiene el tamaño de la ventana y lo convierte en unidades DP con currentWindowSize()
y LocalDensity.current
. Luego, compara el ancho de la ventana para decidir el tipo de diseño de la IU de navegación. Si el ancho de la ventana es de al menos 1200.dp
, usa NavigationSuiteType.NavigationDrawer
. De lo contrario, recurre al cálculo predeterminado.
Cuando vuelvas a ejecutar la app en tu emulador de tamaño variable y pruebes diferentes tipos, observa que cada vez que cambia la configuración de la pantalla o despliegas un dispositivo plegable, la navegación cambia al tipo adecuado para ese tamaño.
¡Felicitaciones! Aprendiste sobre diferentes tipos de navegación para admitir diferentes tipos de tamaños de ventana y estados.
En la siguiente sección, explorarás cómo aprovechar el área restante de la pantalla en lugar de estirar el mismo elemento de la lista de borde a borde.
5. Uso del espacio de la pantalla
No importa si ejecutas la app en una tablet pequeña, un dispositivo desplegado o una tablet grande, la pantalla se estirará para llenar el espacio restante. Asegúrate de que puedas aprovechar ese espacio en pantalla para mostrar más información, como esta app, que muestra el correo electrónico y las conversaciones a los usuarios en la misma página.
Material 3 define tres diseños canónicos, y cada uno tiene configuraciones para clases de tamaño de ventana compacta, mediana y expandida. El diseño canónico de Detalles de lista es perfecto para este caso de uso y está disponible en Compose como ListDetailPaneScaffold
.
- Para obtener este componente, agrega las siguientes dependencias y realiza una sincronización de Gradle:
gradle/libs.versions.toml
[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive.layout)
implementation(libs.androidx.material3.adaptive.navigation)
}
- Busca la función de componibilidad
ReplyAppContent()
enReplyApp.kt
, que por el momento solo muestra el panel de lista llamando aReplyListPane()
. Reemplaza esta implementación porListDetailPaneScaffold
insertando el siguiente código. Dado que esta es una API experimental, también agregarás la anotación@OptIn
en la funciónReplyAppContent()
:
ReplyApp.kt
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
replyHomeUIState: ReplyHomeUIState,
onEmailClick: (Email) -> Unit,
) {
val navigator = rememberListDetailPaneScaffoldNavigator<Long>()
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
ReplyListPane(replyHomeUIState, onEmailClick)
},
detailPane = {
ReplyDetailPane(replyHomeUIState.emails.first())
}
)
}
Este código primero crea un navegador con rememberListDetailPaneNavigator
. El navegador ofrece cierto control sobre qué panel se muestra y qué contenido debe representarse en ese panel, lo que se demostrará más adelante.
ListDetailPaneScaffold
mostrará dos paneles cuando se expanda la clase de tamaño de ancho de ventana. De lo contrario, mostrará un panel o el otro según los valores proporcionados para dos parámetros: la directiva de Scaffold y el valor de Scaffold. Para obtener el comportamiento predeterminado, este código usa la directiva de andamiaje y el valor de andamiaje que proporciona el navegador.
Los parámetros obligatorios restantes son lambdas componibles para los paneles. ReplyListPane()
y ReplyDetailPane()
(que se encuentran en ReplyListContent.kt
) se usan para completar los roles de los paneles de lista y de detalles, respectivamente. ReplyDetailPane()
espera un argumento de correo electrónico, así que, por ahora, este código usa el primer correo electrónico de la lista de correos electrónicos en ReplyHomeUIState
.
Ejecuta la app y cambia la vista del emulador a plegable o tablet (es posible que también debas cambiar la orientación) para ver el diseño de los dos paneles. ¡Esto ya se ve mucho mejor que antes!
Ahora, analicemos parte del comportamiento deseado de esta pantalla. Cuando el usuario presiona un correo electrónico en el panel de lista, este debería aparecer en el panel de detalles junto con todas las respuestas. Actualmente, la app no lleva un registro de qué correo electrónico se seleccionó, y presionar un elemento no hace nada. El mejor lugar para guardar esta información es con el resto del estado de la IU en ReplyHomeUIState
.
- Abre
ReplyHomeViewModel.kt
y busca la clase de datosReplyHomeUIState
. Agrega una propiedad para el correo electrónico seleccionado, con un valor predeterminado denull
:
ReplyHomeViewModel.kt
data class ReplyHomeUIState(
val emails : List<Email> = emptyList(),
val selectedEmail: Email? = null,
val loading: Boolean = false,
val error: String? = null
)
- En el mismo archivo,
ReplyHomeViewModel
tiene una funciónsetSelectedEmail()
a la que se llama cuando el usuario presiona un elemento de la lista. Modifica esta función para copiar el estado de la IU y registrar el correo electrónico seleccionado:
ReplyHomeViewModel.kt
fun setSelectedEmail(email: Email) {
_uiState.update {
it.copy(selectedEmail = email)
}
}
Algo que se debe tener en cuenta es lo que sucede antes de que el usuario presione cualquier elemento y el correo electrónico seleccionado sea null
. ¿Qué debería aparecer en el panel de detalles? Existen varias formas de manejar este caso, como mostrar el primer elemento de la lista de forma predeterminada.
- En el mismo archivo, modifica la función
observeEmails()
. Cuando se cargue la lista de correos electrónicos, si el estado anterior de la IU no tenía un correo electrónico seleccionado, configúralo en el primer elemento:
ReplyHomeViewModel.kt
private fun observeEmails() {
viewModelScope.launch {
emailsRepository.getAllEmails()
.catch { ex ->
_uiState.value = ReplyHomeUIState(error = ex.message)
}
.collect { emails ->
val currentSelection = _uiState.value.selectedEmail
_uiState.value = ReplyHomeUIState(
emails = emails,
selectedEmail = currentSelection ?: emails.first()
)
}
}
}
- Regresa a
ReplyApp.kt
y usa el correo electrónico seleccionado, si está disponible, para propagar el contenido del panel de detalles:
ReplyApp.kt
ListDetailPaneScaffold(
// ...
detailPane = {
if (replyHomeUIState.selectedEmail != null) {
ReplyDetailPane(replyHomeUIState.selectedEmail)
}
}
)
Vuelve a ejecutar la app y cambia el emulador al tamaño de tablet. Observa que presionar un elemento de la lista actualiza el contenido del panel de detalles.
Esto funciona muy bien cuando ambos paneles son visibles, pero cuando la ventana solo tiene espacio para mostrar un panel, parece que no sucede nada cuando presionas un elemento. Intenta cambiar la vista del emulador a un teléfono o dispositivo plegable en orientación vertical, y observa que solo se ve el panel de lista incluso después de presionar un elemento. Esto se debe a que, aunque el correo electrónico seleccionado está actualizado, ListDetailPaneScaffold
se mantiene enfocado en el panel de lista en estos parámetros de configuración.
- Para solucionar este problema, inserta el siguiente código como la expresión lambda que se pasa a
ReplyListPane
:
ReplyApp.kt
ListDetailPaneScaffold(
// ...
listPane = {
ReplyListPane(
replyHomeUIState = replyHomeUIState,
onEmailClick = { email ->
onEmailClick(email)
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
}
)
},
// ...
)
Esta lambda usa el navegador que se creó antes para agregar un comportamiento adicional cuando se hace clic en un elemento. Llama a la expresión lambda original que se pasó a esta función y, luego, también llama a navigator.navigateTo()
, que especifica qué panel se debe mostrar. Cada panel del andamiaje tiene un rol asociado, y para el panel de detalles es ListDetailPaneScaffoldRole.Detail
. En ventanas más pequeñas, mostrará la apariencia de que la app navegó hacia adelante.
La app también necesita controlar lo que sucede cuando el usuario presiona el botón Atrás desde el panel de detalles, y este comportamiento será diferente dependiendo de si hay uno o dos paneles visibles.
- Agrega el siguiente código para admitir la navegación hacia atrás.
ReplyApp.kt
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
replyHomeUIState: ReplyHomeUIState,
onEmailClick: (Email) -> Unit,
) {
val navigator = rememberListDetailPaneScaffoldNavigator<Long>()
BackHandler(navigator.canNavigateBack()) {
navigator.navigateBack()
}
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
AnimatedPane {
ReplyListPane(
replyHomeUIState = replyHomeUIState,
onEmailClick = { email ->
onEmailClick(email)
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
}
)
}
},
detailPane = {
AnimatedPane {
if (replyHomeUIState.selectedEmail != null) {
ReplyDetailPane(replyHomeUIState.selectedEmail)
}
}
}
)
}
El navegador conoce el estado completo de ListDetailPaneScaffold
, si es posible hacer la navegación hacia atrás y qué hacer en todas estas situaciones. Este código crea un BackHandler
que se habilita siempre que el navegador puede navegar hacia atrás y, dentro de la lambda, llama a navigateBack()
. Además, para que la transición entre paneles sea mucho más fluida, cada panel se une en un elemento AnimatedPane()
componible.
Vuelve a ejecutar la app en un emulador de tamaño variable para todos los diferentes tipos de dispositivos y observa que cada vez que cambia la configuración de la pantalla o despliegas un dispositivo plegable, la navegación y el contenido de la pantalla cambian de forma dinámica en respuesta a los cambios de estado del dispositivo. También intenta presionar los correos electrónicos en el panel de lista y observa cómo se comporta el diseño en diferentes pantallas mostrando ambos paneles uno al lado del otro o animando entre ellos de forma fluida.
Felicitaciones. Lograste que tu app se adapte correctamente a todo tipo de estados y tamaños de dispositivos. Puedes ejecutar la app en dispositivos plegables, tablets y otros dispositivos móviles.
6. Felicitaciones
¡Felicitaciones! Completaste correctamente este codelab y aprendiste a hacer apps adaptables con Jetpack Compose.
Aprendiste a verificar el tamaño y el estado de plegado de un dispositivo, y a actualizar la IU, la navegación y otras funciones de tu app adecuadamente. También aprendiste cómo la adaptabilidad mejora la accesibilidad y la experiencia del usuario.
Próximos pasos
Consulta los otros codelabs sobre la ruta de aprendizaje de Compose.
Apps de ejemplo
- Las muestras de Compose son una colección de muchas apps que incorporan las prácticas recomendadas que se explican en codelabs.