1. Introdução
Uma das grandes vantagens de desenvolver seu app na Plataforma Android é a incrível oportunidade de alcançar usuários em diferentes tipos de formatos, como wearables, dobráveis, tablets, computadores e até TV. Talvez os usuários também queiram usar o app em dispositivos de tela grande para aproveitar o aumento do espaço. Cada vez mais, os usuários do Android aproveitam os apps em vários dispositivos com tamanhos de tela variados e esperam uma experiência de alta qualidade em todos esses aparelhos.
Até agora, você aprendeu a criar apps principalmente para dispositivos móveis. Neste codelab, você vai aprender a transformar seus apps para que eles se adaptem a outros tamanhos de tela. Você vai usar padrões de layout de navegação adaptáveis que são bonitos e podem ser usados em dispositivos móveis e de telas grandes, como dobráveis, tablets e computadores.
Pré-requisitos
- Familiaridade com a programação em Kotlin, incluindo classes, funções e condicionais
- Familiaridade com as classes
ViewModel
- Familiaridade com a criação de funções
Composable
- Experiência em criação de layouts com o Jetpack Compose
- Saber executar apps em um dispositivo ou emulador
O que você vai aprender
- Como criar navegação entre telas sem o gráfico de navegação para apps simples
- Como criar um layout de navegação adaptável usando o Jetpack Compose
- Como criar um gerenciador de retorno personalizado
O que você vai criar
- Você vai implementar a navegação dinâmica no app Reply para que o layout se adapte a todos os tamanhos de tela
O produto final vai ser semelhante à imagem abaixo:
O que é necessário
- Um computador com acesso à Internet, um navegador da Web e o Android Studio
- Acesso ao GitHub
2. Visão geral do app
Introdução ao app Reply
O Reply é um app multitelas semelhante a um cliente de e-mail.
Ele contém 4 categorias diferentes que são exibidas por guias distintas: "Inbox", "Sent", "Drafts" e "Spam".
Baixar o código inicial
No Android Studio, abra a pasta basic-android-kotlin-compose-training-reply-app
.
3. Tutorial do código inicial
Diretórios importantes no app Reply
A camada de dados e de UI do projeto do app Reply é separada em diretórios diferentes. O ReplyViewModel
, o ReplyUiState
e outros elementos de composição ficam no diretório ui
. As classes data
e enum
que definem a camada de dados e as classes do provedor de dados estão no diretório data
.
Inicialização de dados no app Reply
O app Reply é inicializado com dados pelo método initializeUIState()
no ReplyViewModel
, que é executado na função 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
)
}
...
O elemento combinável no nível da tela
Assim como em outros apps, o Reply usa o elemento combinável ReplyApp
como sendo o principal, em que viewModel
e uiState
são declarados. Várias funções viewModel()
também são transmitidas como argumentos lambda para o elemento combinável 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
)
}
Outros elementos de composição
ReplyHomeScreen.kt
: contém os elementos de composição de tela inicial, incluindo os elementos de navegação.ReplyHomeContent.kt
: contém elementos de composição que definem elementos mais detalhados da tela inicial.ReplyDetailsScreen.kt
: contém os elementos de composição de tela e outros menores para a tela de detalhes.
Analise cada arquivo em detalhes para entender melhor os elementos combináveis antes de avançar para a próxima seção do codelab.
4. Mudar telas sem um gráfico de navegação
No programa de treinamentos anterior, você aprendeu a usar uma classe NavHostController
para navegar de uma tela a outra. Com o Compose, também é possível mudar telas com instruções condicionais simples, usando estados mutáveis do tempo de execução. Isso é útil especialmente em aplicativos pequenos, como o app Reply, em que você só quer alternar entre duas telas.
Mudar telas com mudanças de estado
No Compose, as telas são recompostas quando ocorre uma mudança de estado. Mude telas usando condicionais simples para responder a mudanças de estado.
Você vai usar condicionais para mostrar o conteúdo da tela inicial quando o usuário estiver nela e exibir a tela de detalhes quando a pessoa não estiver na inicial.
Modifique o app Reply para permitir mudanças na tela quando o estado mudar seguindo estas etapas:
- Abra o código inicial no Android Studio.
- No elemento combinável
ReplyHomeScreen
emReplyHomeScreen.kt
, envolva o elemento combinávelReplyAppContent
com uma instruçãoif
para quando a propriedadeisShowingHomepage
do objetoreplyUiState
fortrue
.
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
)
}
}
Agora você precisa pensar na exibição da tela de detalhes quando o usuário não estiver na tela inicial.
- Adicione uma ramificação
else
com o elemento combinávelReplyDetailsScreen
no corpo. AdicionereplyUIState
,onDetailScreenBackPressed
emodifier
como argumentos do elemento combinávelReplyDetailsScreen
.
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
)
}
}
O objeto replyUiState
é um objeto de estado. Dessa forma, quando há uma mudança na propriedade isShowingHomepage
do objeto replyUiState
, o elemento combinável ReplyHomeScreen
é recomposto, e a instrução if/else
é reavaliada no tempo de execução. Essa abordagem oferece suporte à navegação entre diferentes telas sem o uso de uma classe NavHostController
.
Criar gerenciador de retorno personalizado
Uma vantagem de usar o elemento combinável NavHost
para alternar entre telas é que as rotas das telas anteriores são salvas na pilha de retorno. Essas telas salvas permitem que o botão "Voltar" do sistema navegue com facilidade para a tela anterior quando invocado. Como o app Reply não usa um NavHost
, você precisa adicionar o código para processar o botão "Voltar" manualmente. Você fará isso a seguir.
Conclua as etapas abaixo para criar um gerenciador de retorno personalizado no app Reply:
- Na primeira linha do elemento combinável
ReplyDetailsScreen
, adicione um elementoBackHandler
. - Chame a função
onBackPressed()
no corpo do elemento combinávelBackHandler
.
ReplyDetailsScreen.kt
...
import androidx.activity.compose.BackHandler
...
@Composable
fun ReplyDetailsScreen(
replyUiState: ReplyUiState,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier
) {
BackHandler {
onBackPressed()
}
...
5. Executar aplicativo em dispositivos de tela grande
Verificar o app com o emulador redimensionável
Para criar apps utilizáveis, os desenvolvedores precisam entender a experiência dos usuários em vários formatos. Portanto, você precisa testar apps em vários formatos desde o início do processo de desenvolvimento.
Você pode usar muitos emuladores de diferentes tamanhos de tela para atingir essa meta. No entanto, isso pode ser complicado, especialmente quando você está criando para vários tamanhos de tela de uma só vez. Talvez você também precise testar como um app que está sendo executado responde a mudanças no tamanho da tela, como mudanças de orientação, de tamanho de janela em um computador e de estado de dobra no dobrável.
O Android Studio ajuda a testar esses cenários com a introdução do emulador redimensionável.
Conclua estas etapas para configurar o emulador redimensionável:
- No Android Studio, selecione Tools > Device Manager.
- No Gerenciador de dispositivos, clique no ícone + para criar um dispositivo virtual.
- Selecione a categoria Phone e o dispositivo Resizable (Experimental).
- Clique em Next.
- Selecione o nível da API 34 ou posterior.
- Clique em Next.
- Escolha um nome para o novo Dispositivo Virtual Android.
- Clique em Finish.
Executar aplicativo em um emulador de tela grande
Agora que você configurou o emulador redimensionável, vamos ver como o app aparece em uma tela grande.
- Execute o app no emulador redimensionável.
- Selecione Tablet como modo de exibição.
- Inspecione o app no modo tablet, no modo paisagem.
A tela do tablet é alongada na horizontal. Embora essa orientação funcione, talvez ela não seja o melhor uso do espaço da tela grande. Vamos falar sobre isso a seguir.
Design para telas grandes
Ao analisar este app em um tablet, você pode pensar que ele foi mal projetado e não é atraente. Você acertou: esse layout não foi projetado para telas grandes.
Ao projetar para telas grandes, como tablets e dobráveis, é preciso considerar a ergonomia do usuário e a proximidade dos dedos dos usuários à tela. Em dispositivos móveis, os dedos podem facilmente alcançar a maior parte da tela. A localização dos elementos interativos, como botões e elementos de navegação, não é tão importante. No entanto, em telas grandes, ter elementos interativos importantes no meio da tela pode dificultar o alcance.
Como você pode ver no app Reply, projetar para telas grandes não é apenas alongar ou aumentar elementos de IU para se ajustarem à tela. É uma oportunidade de usar um espaço maior para criar uma experiência diferente para os usuários. Por exemplo, é possível adicionar outro layout na mesma tela para evitar a necessidade de navegar para outra tela ou possibilitar várias tarefas ao mesmo tempo.
Esse design pode aumentar a produtividade do usuário e promover um maior engajamento. Mas, antes de implantar esse design, é necessário aprender a criar layouts diferentes para tamanhos de tela variados.
6. Adaptar o layout a diferentes tamanhos de tela
O que são pontos de interrupção?
Você pode se perguntar como é possível mostrar layouts diferentes para o mesmo app. A resposta curta é: usando condicionais em estados diferentes, como você fez no início deste codelab.
Para criar um app adaptável, o layout precisa mudar de acordo com o tamanho da tela. O ponto de medição em que um layout muda é conhecido como ponto de interrupção. O Material Design criou um intervalo de pontos de interrupção opinativo que abrange a maioria das telas do Android.
Essa tabela de intervalo de pontos de interrupção mostra, por exemplo, que, se o app estiver sendo executado em um dispositivo com tamanho de tela inferior a 600 dp, você vai precisar mostrar o layout para dispositivos móveis.
Usar classes de tamanho de janela
A API WindowSizeClass
introduzida para o Compose simplifica a implementação de pontos de interrupção do Material Design.
As classes de tamanho de janela introduzem três categorias de tamanhos: compacto, médio e expandido, para largura e altura.
Conclua as etapas a seguir para implementar a API WindowSizeClass
no app Reply:
- Adicione a dependência
material3-window-size-class
ao arquivobuild.gradle.kts
do módulo.
build.gradle.kts
...
dependencies {
...
implementation("androidx.compose.material3:material3-window-size-class")
...
- Clique em Sync Now para sincronizar o Gradle depois de adicionar a dependência.
Com o arquivo build.gradle.kts
atualizado, agora você pode criar uma variável que armazena o tamanho da janela do app a qualquer momento.
- Na função
onCreate()
do arquivoMainActivity.kt
, atribua o métodocalculateWindowSizeClass()
com o contextothis
transmitido no parâmetro a uma variável chamadawindowSize
. - Importe o pacote
calculateWindowSizeClass
adequado.
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReplyTheme {
val layoutDirection = LocalLayoutDirection.current
Surface (
// ...
) {
val windowSize = calculateWindowSizeClass(this)
ReplyApp()
...
- Observe o sublinhado vermelho da sintaxe
calculateWindowSizeClass
, que mostra a lâmpada vermelha. Clique na lâmpada vermelha à esquerda da variávelwindowSize
e selecione Opt in for ‘ExperimentalMaterial3WindowSizeClassApi' on ‘onCreate' para criar uma anotação sobre o métodoonCreate()
.
Você pode usar a variável WindowWidthSizeClass
no MainActivity.kt
para determinar qual layout exibir em vários elementos combináveis. Vamos preparar o elemento combinável ReplyApp
para receber esse valor.
- No arquivo
ReplyApp.kt
, modifique o elemento combinávelReplyApp
para aceitar aWindowWidthSizeClass
como o parâmetro e importar o pacote adequado.
ReplyApp.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
...
- Transmita a variável
windowSize
ao componenteReplyApp
no métodoonCreate()
do arquivoMainActivity.kt
.
MainActivity.kt
...
setContent {
ReplyTheme {
Surface {
val windowSize = calculateWindowSizeClass(this)
ReplyApp(
windowSize = windowSize.widthSizeClass
)
...
Também será necessário atualizar a visualização do app para o parâmetro windowSize
.
- Transmita o
WindowWidthSizeClass.Compact
como o parâmetrowindowSize
para o elemento combinávelReplyApp
do componente de visualização e importe o pacote adequado.
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Preview(showBackground = true)
@Composable
fun ReplyAppCompactPreview() {
ReplyTheme {
Surface {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact,
)
}
}
}
- Para mudar os layouts do app com base no tamanho da tela, adicione uma instrução
when
no elemento combinávelReplyApp
com base no valor deWindowWidthSizeClass
.
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 -> {
}
}
...
Neste ponto, você estabeleceu uma base para usar os valores de WindowSizeClass
para mudar os layouts no app. A próxima etapa é determinar como você quer que o app seja exibido em diferentes tamanhos de tela.
7. Implementar o layout da navegação adaptável
Implementar a navegação adaptável da IU
No momento, a navegação na parte de baixo é usada para todos os tamanhos de tela.
Como discutido anteriormente, esse elemento de navegação não é ideal, porque os usuários podem ter dificuldade para alcançar elementos essenciais em telas maiores. Felizmente, há padrões recomendados para diferentes elementos de navegação em várias classes de tamanho de janela na navegação para IUs responsivas. Para o app Reply, você pode implementar os seguintes elementos:
A coluna de navegação é outro componente do Material Design que permite opções de navegação compactas para destinos principais que podem ser acessadas na lateral do app.
Da mesma forma, uma gaveta de navegação persistente/permanente é criada pelo Material Design como outra opção para oferecer acesso ergonômico a telas maiores.
Implementar uma gaveta de navegação
Para criar uma gaveta de navegação para telas expandidas, use o parâmetro navigationType
. Conclua as etapas a seguir para fazer isso:
- Para representar diferentes tipos de elementos de navegação, crie um novo arquivo
WindowStateUtils.kt
em um novo pacoteutils
, que está no diretórioui
. - Adicione uma classe
Enum
para representar diferentes tipos de elementos de navegação.
WindowStateUtils.kt
package com.example.reply.ui.utils
enum class ReplyNavigationType {
BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
}
Para implementar a gaveta de navegação, você precisa determinar o tipo de navegação com base no tamanho da janela do app.
- No elemento combinável
ReplyApp
, crie uma variávelnavigationType
e atribua a ela o valorReplyNavigationType
adequado, de acordo com o tamanho da tela na instruçãowhen
.
ReplyApp.kt
...
import com.example.reply.ui.utils.ReplyNavigationType
...
val navigationType: 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
}
}
...
Você pode usar o valor navigationType
no elemento combinável ReplyHomeScreen
. Você pode se preparar para isso tornando-o um parâmetro para o elemento combinável.
- No elemento combinável
ReplyHomeScreen
, adicionenavigationType
como um parâmetro.
ReplyHomeScreen.kt
...
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit,
onEmailCardPressed: (Email) -> Unit,
onDetailScreenBackPressed: () -> Unit,
modifier: Modifier = Modifier
)
...
- Transmita o
navigationType
para o elemento combinávelReplyHomeScreen
.
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
)
...
Em seguida, você pode criar uma ramificação para mostrar o conteúdo do app com uma gaveta de navegação quando o usuário abrir o app em uma tela expandida e exibir a tela inicial.
- No corpo do elemento combinável
ReplyHomeScreen
, adicione uma instruçãoif
para a condiçãonavigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER && replyUiState.isShowingHomepage
.
ReplyHomeScreen.kt
import androidx.compose.material3.PermanentNavigationDrawer
...
@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,
...
- Para criar a gaveta permanente, crie o elemento combinável
PermanentNavigationDrawer
no corpo da instrução "if" e adicione o elemento combinávelNavigationDrawerContent
como entrada para o parâmetrodrawerContent
. - Adicione o elemento combinável
ReplyAppContent
como o argumento lambda final dePermanentNavigationDrawer
.
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
PermanentDrawerSheet(Modifier.width(dimensionResource(R.dimen.drawer_width))) {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight()
.background(MaterialTheme.colorScheme.inverseOnSurface)
.padding(dimensionResource(R.dimen.drawer_padding_content))
)
}
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
...
- Adicione uma ramificação
else
que use o corpo do elemento combinável anterior para manter a ramificação anterior em telas não expandidas.
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
PermanentDrawerSheet(Modifier.width(dimensionResource(R.dimen.drawer_width))) {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight()
.background(MaterialTheme.colorScheme.inverseOnSurface)
.padding(dimensionResource(R.dimen.drawer_padding_content))
)
}
}
) {
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
)
}
}
}
...
- Execute o app no modo Tablet. Você verá a seguinte tela:
Implementar uma coluna de navegação
Assim como na implementação da gaveta de navegação, é necessário usar o parâmetro navigationType
para alternar entre os elementos de navegação.
Primeiro, vamos adicionar uma coluna de navegação para telas médias.
- Comece com a preparação do elemento combinável
ReplyAppContent
, adicionandonavigationType
como um parâmetro.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit),
onEmailCardPressed: (Email) -> Unit,
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
...
- Transmita o valor
navigationType
para os dois elementos de composiçãoReplyAppContent
.
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
)
...
Em seguida, vamos adicionar ramificações, que permitem que o app mostre colunas de navegação para alguns cenários.
- Na primeira linha do corpo do elemento combinável
ReplyAppContent
, envolva o elemento combinávelReplyNavigationRail
ao redor do elementoAnimatedVisibility
e defina o parâmetrovisible
comotrue
se o valorReplyNavigationType
forNAVIGATION_RAIL
.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit),
onEmailCardPressed: (Email) -> Unit,
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
Box(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)
.padding(
horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.fillMaxWidth()
)
}
}
}
...
- Para alinhar os elementos de composição corretamente, envolva
AnimatedVisibility
eColumn
encontrados no corpoReplyAppContent
em um 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) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
val navigationRailContentDescription = stringResource(R.string.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)
.padding(
horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.fillMaxWidth()
)
}
}
}
...
Por fim, vamos garantir que a navegação na parte de baixo seja exibida em alguns cenários.
- Após o elemento combinável
ReplyListOnlyContent
, envolva oReplyBottomNavigationBar
com um elementoAnimatedVisibility
. - Defina o parâmetro
visible
quando o valorReplyNavigationType
forBOTTOM_NAVIGATION
.
ReplyHomeScreen.kt
...
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
.padding(
horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
)
)
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
val bottomNavigationContentDescription = stringResource(R.string.navigation_bottom)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.fillMaxWidth()
)
}
...
- Execute o app no modo Unfolded foldable. Você vai ver a seguinte tela:
8. Acessar o código da solução
Para baixar o código do codelab concluído, use estes comandos 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
Se preferir, você pode baixar o repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Para conferir o código da solução, acesse o GitHub (link em inglês).
9. Conclusão
Parabéns! Você está a prestes a tornar o app Reply adaptável para todos os tamanhos de tela implementando um layout de navegação adaptável. Você melhorou a experiência do usuário usando muitos formatos no Android. No próximo codelab, você vai melhorar ainda mais suas habilidades ao trabalhar com apps adaptáveis, implementando layouts, testes e visualizações de conteúdo adaptável.
Não se esqueça de compartilhar seu trabalho nas redes sociais com a hashtag #AndroidBasics.