1. Introducción
Una de las grandes ventajas de desarrollar tu app en la plataforma de Android es la gran oportunidad de llegar a los usuarios en diferentes tipos de factores de forma, como wearables, plegables, tablets, computadoras de escritorio e incluso TVs. Cuando se utiliza una app, es posible que los usuarios deseen utilizar la misma aplicación en dispositivos con pantallas grandes para aprovechar el aumento de espacio. Cada vez más, los usuarios de Android utilizan sus apps en varios dispositivos de diferentes tamaños de pantalla y esperan que los usuarios tengan una experiencia de alta calidad en todos los dispositivos.
Hasta ahora, aprendiste a crear apps principalmente para dispositivos móviles. En este codelab, aprenderás a transformarlas para que se adapten a otros tamaños de pantalla. Usarás patrones de diseño de navegación adaptables y atractivos tanto para dispositivos móviles como para pantallas grandes, como plegables, tablets y computadoras de escritorio.
Requisitos previos
- Conocimientos de programación de Kotlin, incluidas clases, funciones y condicionales
- Conocimiento de clases
ViewModel
- Conocimientos sobre la creación de funciones
Composables
- Experiencia en la compilación de diseños con Jetpack Compose
- Experiencia en la ejecución de apps en un dispositivo o emulador
Qué aprenderás
- Cómo crear la navegación entre pantallas sin el gráfico de navegación para apps simples
- Cómo crear un diseño de navegación adaptable con Jetpack Compose
- Cómo crear un controlador de retroceso personalizado
Qué compilarás
- Implementarás la navegación dinámica en la app de Reply existente para que su diseño se adapte a todos los tamaños de pantalla.
El producto final se verá como la siguiente imagen:
Requisitos
- Una computadora con acceso a Internet, un navegador web y Android Studio
- Acceso a GitHub
2. Descripción general de la app
Introducción a la app de Reply
La app de Reply es multipantalla, similar a un cliente de correo electrónico.
Contiene 4 categorías diferentes que se muestran en distintas pestañas: Recibidos, Enviados, Borradores y Spam.
Descarga el código de inicio
En Android Studio, abre la carpeta basic-android-kotlin-compose-training-reply-app
.
- Navega a la página de repositorio de GitHub del proyecto.
- Verifica que el nombre de la rama coincida con el especificado en el codelab. Por ejemplo, en la siguiente captura de pantalla, el nombre de la rama es main.
- En la página de GitHub de este proyecto, haz clic en el botón Code, el cual abre una ventana emergente.
- En la ventana emergente, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
- Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
- Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.
Abre el proyecto en Android Studio
- Inicia Android Studio.
- En la ventana Welcome to Android Studio, haz clic en Open.
Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > Open.
- En el navegador de archivos, ve hasta donde se encuentra la carpeta de proyecto descomprimido (probablemente en Descargas).
- Haz doble clic en la carpeta del proyecto.
- Espera a que Android Studio abra el proyecto.
- Haz clic en el botón Run para compilar y ejecutar la app. Asegúrate de que funcione como se espera.
3. Explicación del código de inicio
Directorios importantes en la app de Reply
La capa de IU y datos del proyecto de la app de Reply se divide en directorios diferentes. ReplyViewModel
, ReplyUiState
y otros elementos de componibilidad se encuentran en el directorio ui
. Las clases data
y enum
que definen la capa de datos y las clases de proveedores de datos se encuentran en el directorio data
.
Inicialización de datos en la app de Reply
La app de Reply se inicializa con datos a través del método initilizeUIState()
en el ReplyViewModel
, que se ejecuta en la función init
.
ReplyViewModel.kt
...
init {
initializeUIState()
}
private fun initializeUIState() {
var mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
...
El elemento de componibilidad de nivel de pantalla
Al igual que con otras apps, la app de Reply usa el elemento de componibilidad ReplyApp
como el principal donde se declaran viewModel
y uiState
. También se pasan varias funciones viewModel
como argumentos lambda para el elemento de componibilidad ReplyHomeScreen
.
ReplyApp.kt
...
@Composable
fun ReplyApp(modifier: Modifier = Modifier) {
val viewModel: ReplyViewModel = viewModel()
val replyUiState = viewModel.uiState.collectAsState().value
ReplyHomeScreen(
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
}
Otros elementos de componibilidad
ReplyHomeScreen.kt
: contiene los elementos de componibilidad de la pantalla principal, incluidos los elementos de navegación.ReplyHomeContent.kt
: contiene elementos de componibilidad que definen elementos más detallados de la pantalla principal.ReplyDetailsScreen.kt
: contiene elementos de componibilidad de pantalla y elementos más pequeños para la pantalla de detalles.
No dudes en revisar cada archivo en detalle a fin de comprender mejor los elementos de componibilidad antes de continuar con la siguiente sección del codelab.
4. Cómo cambiar de pantalla sin un gráfico de navegación
En la ruta anterior, aprendiste a usar una clase NavHostController
para navegar de una pantalla a otra. Con Compose, también puedes cambiar pantallas con declaraciones condicionales simples si usas los estados mutables del tiempo de ejecución. Esto es especialmente útil en aplicaciones pequeñas como la app de Reply, en la que solo quieres alternar entre dos pantallas.
Cómo cambiar de pantalla con cambios de estado
En Compose, las pantallas se recomponen cuando se produce un cambio de estado. Puedes cambiar las pantallas mediante condicionales simples para responder a los cambios de estado.
Usarás condicionales a fin de mostrar el contenido de la pantalla principal cuando el usuario esté en ella y la de detalles cuando el usuario no lo esté.
Modifica la app de Reply a los efectos de permitir cambios de pantalla cuando cambie el estado. Para ello, completa los siguientes pasos:
- Abre el código de inicio en Android Studio.
- En el elemento de componibilidad
ReplyHomeScreen
enReplyHomeScreen.kt
, une el elementoReplyAppContent
con una sentenciaif
para cuando la propiedadisShowingHomepage
del objetoreplyUiState
seatrue
.
ReplyHomeScreen.kt
@Composable
fun ReplyHomeScreen(
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Int) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
Ahora debes tener en cuenta la situación cuando el usuario no esté en la pantalla principal mostrando la pantalla de detalles.
- Agrega una rama
else
con el elemento de componibilidadReplyDetailsScreen
en su cuerpo. AgregareplyUIState
,onDetailScreenBackPressed
ymodifier
como argumentos para el elemento de componibilidadReplyDetailsScreen
.
ReplyHomeScreen.kt
@Composable
fun ReplyHomeScreen(
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Int) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
}
El objeto replyUiState
es un objeto de estado. Por lo tanto, cuando hay un cambio en la propiedad isShowingHomepage
del objeto replyUiState
, el elemento de componibilidad ReplyHomeScreen
se vuelve a componer y la sentencia if/else
se vuelve a evaluar en el tiempo de ejecución. Este enfoque admite la navegación entre diferentes pantallas sin el uso de una clase NavHostController
.
Cómo crear un controlador de retroceso personalizado
Una ventaja de usar el elemento de componibilidad NavHost
para cambiar de pantalla es que las instrucciones de las pantallas anteriores se guardan en la pila de actividades. Estas pantallas guardadas permiten que el botón Atrás del sistema navegue fácilmente a la pantalla anterior cuando se lo invoca. Dado que la app de Reply no usa un NavHost
, debes agregar el código para controlar el botón Atrás de forma manual. Lo harás a continuación.
Completa los siguientes pasos a fin de crear un controlador de retroceso personalizado en la app de Reply:
- En la primera línea del elemento de componibilidad
ReplyDetailsScreen
, agrega un elementoBackHandler
. - Llama a la función
onBackPressed()
en el cuerpo del elemento de componibilidadBackHandler
.
ReplyDetailsScreen.kt
...
import androidx.activity.compose.BackHandler
...
@Composable
fun ReplyDetailsScreen(
replyUiState: ReplyUiState,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
) {
BackHandler {
onBackPressed()
}
...
5. Cómo ejecutar la app en dispositivos con pantallas grandes
Cómo verificar tu app con el emulador de tamaño variable
A fin de crear apps usables, los desarrolladores deben comprender la experiencia de sus usuarios en varios factores de forma. Por lo tanto, debes probar apps en varios de esos factores desde el comienzo del proceso de desarrollo.
Para lograr este objetivo, puedes usar muchos emuladores de diferentes tamaños de pantalla. Sin embargo, hacerlo puede resultar engorroso, en especial cuando compilas contenido para diferentes tamaños de pantalla a la vez. Es posible que también debas probar cómo una app en ejecución responde a los cambios de tamaño de la pantalla, como los cambios de orientación, los de tamaño de ventana en computadoras de escritorio y los de estado de plegado en dispositivos plegables.
Android Studio te permite probar estas situaciones con la introducción del emulador de tamaño variable.
Completa los siguientes pasos para configurar el emulador de tamaño variable:
- Asegúrate de ejecutar Android Studio Chipmunk | 2021.2.1 o una versión posterior.
- En Android Studio, selecciona Tools > Device Manager.
- En el Administrador de dispositivos, haz clic en Crear dispositivo.
- Selecciona la categoría Phone y el dispositivo Resizable (Experimental).
- Haz clic en Siguiente.
- Selecciona el nivel de API 33.
- Haz clic en Siguiente.
- Asigna un nombre al nuevo dispositivo virtual de Android.
- Haz clic en Finish.
Cómo ejecutar la app en un emulador de pantallas grandes
Ahora que tienes la configuración del emulador de tamaño variable, veamos cómo se ve la app en una pantalla grande.
- Ejecuta la app en el emulador de tamaño variable.
- Selecciona Tablet en el modo de visualización.
- Inspecciona la app en el modo tablet en modo horizontal.
Observa que la pantalla de la tablet se alarga horizontalmente. Si bien esta orientación funciona, es posible que no sea el mejor uso del espacio en pantallas grandes. Analicemos eso a continuación.
Cómo diseñar para pantallas grandes
Lo primero que piensas cuando miras esta app en una tablet es que no está bien diseñada y no resulta atractiva. Tienes toda la razón: este diseño no está creado para pantallas grandes.
Cuando diseñes para pantallas grandes, como tablets y plegables, debes tener en cuenta la ergonomía y la proximidad de los dedos de los usuarios a la pantalla. Con los dispositivos móviles, los dedos de los usuarios pueden llegar con facilidad a la mayor parte de la pantalla. La ubicación de los elementos interactivos, como los botones y los elementos de navegación, no es tan importante. Sin embargo, en el caso de pantallas grandes, tener elementos interactivos esenciales en el medio de ellas puede dificultar su alcance.
Como puedes ver en la app de Reply, diseñar para pantallas grandes no consiste solo en estirar ni ampliar elementos de la IU de modo que se ajusten a la pantalla. Es una oportunidad de usar el mayor espacio a fin de crear una experiencia diferente para sus usuarios. Por ejemplo, puedes agregar otro diseño en la misma pantalla, y así evitar la necesidad de navegar a otra, o realizar varias tareas al mismo tiempo.
Este diseño puede aumentar la productividad de los usuarios y fomenta una mayor participación. Pero antes de implementar este diseño, debes aprender a crear diferentes diseños para distintos tamaños de pantalla.
6. Cómo hacer que tu diseño se adapte a diferentes tamaños de pantalla
¿Qué son los puntos de interrupción?
Es posible que te preguntes cómo mostrar diferentes diseños para la misma app. La respuesta corta es: usando condicionales en diferentes estados, como lo hiciste al comienzo de este codelab.
Para crear una app adaptable, necesitas que el diseño cambie en función del tamaño de la pantalla. El punto de medición en el que cambia un diseño se conoce como punto de interrupción. Material Design creó un rango de puntos de interrupción bien definidos que abarca la mayoría de las pantallas de Android.
Esta tabla de rangos de puntos de interrupción muestra, por ejemplo, que si tu app se está ejecutando en un dispositivo con un tamaño de pantalla inferior a 600 dp, debes mostrar el diseño para dispositivos móviles.
Cómo usar clases de tamaño de ventana
La API de WindowSizeClass
que se introdujo para Compose facilita la implementación de los puntos de interrupción de Material Design.
Las clases de tamaño de ventana presentan tres categorías de tamaños: compacto, mediano y expandido, tanto para el ancho como para la altura.
Completa los siguientes pasos a fin de implementar la API de WindowSizeClass
en la app de Reply:
- Agrega la dependencia
material3-window-size-class
al archivobuild.gradle
del módulo.
build.gradle
...
dependencies {
...
"androidx.compose.material3:material3-window-size-class:$material3_version"
...
- Haz clic en Sync Now para sincronizar Gradle después de agregar la dependencia.
Con el archivo build.grade
actualizado, ahora puedes crear una variable que almacene el tamaño de la ventana de la app en cualquier momento.
- En la función
onCreate()
del archivoMainActivity.kt
, asigna el métodocalculateWindowSizeClass
con el contextothis
pasado en el parámetro a una variable llamadawindowSize
. - Importa el paquete
calculateWindowSizeClass
adecuado.
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReplyTheme {
val windowSize = calculateWindowSizeClass(this)
ReplyApp()
...
- Observa el subrayado rojo de la sintaxis
calculateWindowSizeClass
, que muestra la bombilla roja. Haz clic en la bombilla roja a la izquierda de la variablewindowSize
y selecciona Opt in for 'ExperimentalMaterial3WindowSizeClassApi' on 'onCreate' para crear una anotación sobre el métodoonCreate()
.
Puedes usar la variable WindowWidthSizeClass
en MainActivity.kt
a fin de determinar qué diseño se mostrará en varios elementos de componibilidad. Preparemos el elemento ReplyApp
para recibir este valor.
- En el archivo
ReplyApp.kt
, modifica el elemento de componibilidadReplyApp
de modo que acepteWindowWidthSizeClass
como parámetro y, luego, importa el paquete correspondiente.
ReplyApp.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
...
- Pasa la variable
windowSize
al componenteReplyApp
en el métodoonCreate()
del archivoMainActivity.kt
.
MainActivity.kt
...
setContent {
ReplyTheme {
val windowSize = calculateWindowSizeClass(this)
ReplyApp(
windowSize = windowSize.widthSizeClass
)
...
También debes actualizar la vista previa de la app para el parámetro windowSize
.
- Pasa
WindowWidthSizeClass.Compact
como parámetrowindowSize
al elemento de componibilidadReplyApp
para el componente de vista previa y, luego, importa el paquete correspondiente.
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Preview(showBackground = true)
@Composable
fun ReplyAppPreview() {
ReplyTheme {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact,
)
}
}
- A fin de cambiar los diseños de la app según el tamaño de la pantalla, agrega una sentencia
when
en el elemento de componibilidadReplyApp
en función del valorWindowWidthSizeClass
.
ReplyApp.kt
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
val viewModel: ReplyViewModel = viewModel()
val replyUiState = viewModel.uiState.collectAsState().value
when (windowSize) {
WindowWidthSizeClass.Compact -> {
}
WindowWidthSizeClass.Medium -> {
}
WindowWidthSizeClass.Expanded -> {
}
else -> {
}
}
...
En este punto, estableciste una base para usar valores WindowSizeClass
a los efectos de cambiar los diseños en tu app. El siguiente paso es determinar cómo quieres que se vea tu app en diferentes tamaños de pantalla.
7. Cómo implementar el diseño de navegación adaptable
Cómo implementar la navegación de IU adaptable
Actualmente, se usa la navegación inferior para todos los tamaños de pantalla.
Como se mencionó con anterioridad, este elemento de navegación no es ideal, ya que los usuarios pueden tener dificultades a la hora de acceder a estos elementos esenciales de navegación en pantallas más grandes. Afortunadamente, existen patrones recomendados para diferentes elementos de este tipo en varias clases de tamaño de ventana de navegación para IU responsivas. En la app de Reply, puedes implementar los siguientes elementos:
El riel de navegación es otro componente de navegación de Material Design que permite acceder a opciones de navegación compactas para destinos principales desde un lado de la app.
Del mismo modo, Material Design crea un panel lateral de navegación persistente/permanente como otra opción a los efectos de proporcionar acceso ergonómico a pantallas más grandes.
Cómo implementar un panel lateral de navegación
Si quieres crear un panel lateral de navegación para pantallas expandidas, puedes usar el parámetro navigationType
. Para hacerlo, completa los siguientes pasos:
- A fin de representar diferentes tipos de elementos de navegación, crea un archivo
WindowStateUtils.kt
en un paquete nuevoutils
, que se encuentra en el directorioui
. - Agrega una clase
Enum
para representar diferentes tipos de elementos de navegación.
WindowStateUtils.kt
package com.example.reply.ui.utils
enum class ReplyNavigationType {
BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
}
A fin de implementar con éxito el panel lateral de navegación, debes determinar el tipo de navegación según el tamaño de la ventana de la app.
- En el elemento de componibilidad
ReplyApp
, crea una variablenavigationType
y asígnale el valorReplyNavigationType
adecuado en función del tamaño de la pantalla en la sentenciawhen
.
ReplyApp.kt
...
import com.example.reply.ui.utils.ReplyNavigationType
...
when (windowSize) {
WindowWidthSizeClass.Compact -> {
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
}
WindowWidthSizeClass.Medium -> {
navigationType = ReplyNavigationType.NAVIGATION_RAIL
}
WindowWidthSizeClass.Expanded -> {
navigationType = ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
}
else -> {
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
}
}
...
Puedes usar el valor navigationType
en el elemento de componibilidad ReplyHomeScreen
. Puedes prepararte si lo conviertes en un parámetro para el elemento de componibilidad.
- En el elemento
ReplyHomeScreen
, agreganavigationType
como parámetro.
ReplyHomeScreen.kt
...
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
)
...
- Pasa el
navigationType
al elemento de componibilidadReplyHomeScreen
.
ReplyApp.kt
...
ReplyHomeScreen(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
...
A continuación, puedes crear una rama que exhiba el contenido de la app con un panel lateral de navegación cuando el usuario abra la app en una pantalla expandida y muestre la pantalla principal.
- En el cuerpo del elemento de componibilidad
ReplyHomeScreen
, agrega una sentenciaif
para la condiciónnavigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER && replyUiState.isShowingHomepage
.
ReplyHomeScreen.kt
import androidx.compose.material3.PermanentNavigationDrawer
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
}
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
...
- A fin de crear el panel lateral permanente, crea el elemento de componibilidad
PermanentNavigationDrawer
en el cuerpo de la sentencia if y agrega el elementoNavigationDrawerContent
como entrada para el parámetrodrawerContent
. - Agrega el elemento de componibilidad
ReplyAppContent
como argumento lambda final dePermanentNavigationDrawer
.
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
...
- Agrega una rama
else
que use el cuerpo del elemento de componibilidad anterior a fin de mantener la ramificación previa para pantallas no expandidas.
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
}
}
...
- Agrega una anotación experimental al elemento de componibilidad
ReplyHomeScreen
. La necesitas, ya que la API dePermanentNavigationDrawer
aún es experimental.
ReplyHomeScreen.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
- Ejecuta la app en el modo Tablet. Deberías ver la siguiente pantalla:
Cómo implementar un riel de navegación
Al igual que la implementación del panel lateral de navegación, debes usar el parámetro navigationType
a los efectos de alternar entre elementos de navegación.
Primero, agregaremos un riel de navegación para pantallas medianas.
- Comienza con la preparación del elemento de componibilidad
ReplyAppContent
. A tal fin, agreganavigationType
como parámetro.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
...
- Pasa el valor
navigationType
a ambos elementos de componibilidadReplyAppContent
.
ReplyHomeScreen.kt
...
ReplyAppContent(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
...
A continuación, agregaremos ramas, que permiten a la app mostrar rieles de navegación para algunas situaciones.
- En la primera línea del cuerpo del elemento de componibilidad
ReplyAppContent
, une el elemento de componibilidadReplyNavigationRail
alrededor del elemento de componibilidadAnimatedVisibility
y establece el parámetrovisibility
entrue
si el valor deReplyNavigationType
esNavigationRail
.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
ReplyNavigationRail(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
Column(
modifier = Modifier
.fillMaxSize() .background(MaterialTheme.colorScheme.inverseOnSurface)
) {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
...
- Para alinear correctamente los elementos de componibilidad, une los elementos de componibilidad
AnimatedVisibility
yColumn
que se encuentran en el cuerpo deReplyAppContent
en un elementoRow
.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
Row(modifier = modifier.fillMaxSize()) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
ReplyNavigationRail(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
Column(
modifier = Modifier
.fillMaxSize() .background(MaterialTheme.colorScheme.inverseOnSurface)
) {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
}
...
Por último, asegurémonos de que la navegación inferior se muestre en algunas situaciones.
- Después del elemento de componibilidad
ReplyListOnlyContent
, une el elementoReplyBottomNavigationBar
con un elementoAnimatedVisibility
. - Establece el parámetro
visible
cuando el valor deReplyNavigationType
seaBOTTOM_NAVIGATION
.
ReplyHomeScreen.kt
...
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
...
- Ejecuta la app en el modo de dispositivo plegable desplegado. Deberías ver la siguiente pantalla:
8. Obtén el código de la solución
Para descargar el código del codelab terminado, puedes usar este comando de git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git cd basic-android-kotlin-compose-training-reply-app git checkout nav-update
También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.
Si deseas ver el código de la solución, puedes hacerlo en GitHub.
9. Conclusión
¡Felicitaciones! Estás un paso más cerca de hacer que la app de Reply se adapte a todos los tamaños de pantalla implementando un diseño de navegación adaptable. Mejoraste la experiencia del usuario con muchos factores de forma de Android. En el siguiente codelab, mejorarás aún más tus habilidades de trabajo con apps adaptables mediante la implementación de diseño, pruebas y vistas previas de contenido adaptable.
No olvides compartir tu trabajo en redes sociales con el hashtag #AndroidBasics.