Criar um layout de detalhes e listas

Detalhes e listas é um padrão de IU que consiste em um layout de painel duplo em que um painel apresenta uma lista de itens e outro mostra os detalhes dos itens selecionados na lista.

O padrão é particularmente útil para aplicativos que fornecem informações detalhadas sobre elementos de grandes coleções, por exemplo, um cliente de e-mail que tem uma lista de e-mails e o conteúdo detalhado de cada mensagem. Os detalhes e listas também podem ser usados para caminhos menos críticos, como dividir as preferências do app em uma lista de categorias com as preferências de cada categoria no painel de detalhes.

Um painel de detalhes mostrado ao lado da página de lista.
Figura 1. Quando há espaço suficiente na tela, o painel de detalhes é mostrado ao lado do painel de lista.
Depois que um item é selecionado, o painel de detalhes ocupa a tela inteira.
Figura 2. Quando o tamanho da tela é limitado, o painel de detalhes (já que um item foi selecionado) ocupa todo o espaço.

Implementar o padrão de detalhes e listas com NavigableListDetailPaneScaffold

NavigableListDetailPaneScaffold é um elemento combinável que simplifica a implementação de um layout de detalhes e listas no Jetpack Compose. Ele envolve ListDetailPaneScaffold e adiciona navegação integrada e animações de volta preditivas.

Um scaffold de detalhes e listas oferece suporte a até três painéis:

  1. Painel de lista: mostra uma coleção de itens.
  2. Painel de detalhes: mostra os detalhes de um item selecionado.
  3. Painel extra (opcional): fornece contexto adicional quando necessário.

O scaffold se adapta com base no tamanho da janela:

  • Em janelas grandes, os painéis de lista e de detalhes aparecem lado a lado.
  • Em janelas pequenas, apenas um painel fica visível por vez, alternando conforme os usuários navegam.

Declarar dependências

NavigableListDetailPaneScaffold faz parte da biblioteca de navegação adaptável do Material 3.

Adicione as três dependências relacionadas a seguir ao arquivo build.gradle do app ou módulo:

Kotlin

implementation("androidx.compose.material3.adaptive:adaptive")
implementation("androidx.compose.material3.adaptive:adaptive-layout")
implementation("androidx.compose.material3.adaptive:adaptive-navigation")

Groovy

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • adaptive: blocos de construção de baixo nível, como HingeInfo e Posture
  • adaptive-layout: layouts adaptáveis, como ListDetailPaneScaffold e SupportingPaneScaffold
  • adaptive-navigation: elementos combináveis para navegar dentro e entre painéis, bem como layouts adaptáveis que oferecem suporte à navegação por padrão, como NavigableListDetailPaneScaffold e NavigableSupportingPaneScaffold

Verifique se o projeto inclui a versão 1.1.0-beta1 ou mais recente do compose-material3-adaptive.

Ativar o gesto de volta preditivo

Para ativar animações de volta preditivas no Android 15 ou versões anteriores, é necessário ativar o suporte ao gesto de volta preditivo. Para ativar, adicione android:enableOnBackInvokedCallback="true" à tag <application> ou às tags <activity> individuais no arquivo AndroidManifest.xml. Para mais informações, consulte Ativar o gesto de volta preditivo.

Quando o app é destinado ao Android 16 (nível 36 da API) ou mais recente, a volta preditiva é ativada por padrão.

Uso básico

Implemente NavigableListDetailPaneScaffold da seguinte maneira:

  1. Use uma classe que represente o conteúdo selecionado. Use uma Parcelable classe para oferecer suporte ao salvamento e à restauração do item de lista selecionado. Use o plug-in kotlin-parcelize para gerar o código.
  2. Crie um ThreePaneScaffoldNavigator com rememberListDetailPaneScaffoldNavigator.

Esse navegador é usado para se mover entre os painéis de lista, detalhes e extras. Ao declarar um tipo genérico, o navegador também rastreia o estado do scaffold (ou seja, qual MyItem está sendo mostrado). Como esse tipo é parcelável, o estado pode ser salvo e restaurado pelo navegador para processar automaticamente as mudanças de configuração.

  1. Transmita o navegador para o elemento combinável NavigableListDetailPaneScaffold.

  2. Forneça a implementação do painel de lista para NavigableListDetailPaneScaffold. Use AnimatedPane para aplicar as animações de painel padrão durante a navegação. Em seguida, use ThreePaneScaffoldNavigator para navegar até o painel de detalhes, ListDetailPaneScaffoldRole.Detail, e mostrar o item transmitido.

  3. Inclua a implementação do painel de detalhes em NavigableListDetailPaneScaffold.

Quando a navegação é concluída, currentDestination contém o painel para o qual o app navegou, incluindo o conteúdo mostrado no painel. A propriedade contentKey é o mesmo tipo especificado na chamada original, para que você possa acessar todos os dados que precisa mostrar.

  1. Opcionalmente, mude o defaultBackBehavior em NavigableListDetailPaneScaffold. Por padrão, NavigableListDetailPaneScaffold usa PopUntilScaffoldValueChange para defaultBackBehavior.

Se o app exigir um padrão de navegação de retorno diferente, você poderá substituir esse comportamento especificando outra opção BackNavigationBehavior.

Opções BackNavigationBehavior

A seção a seguir usa o exemplo de um app de e-mail com uma lista de e-mails em um painel e uma visualização detalhada no outro.

Esse comportamento se concentra em mudanças na estrutura geral do layout. Em uma configuração de vários painéis, mudar o conteúdo do e-mail no painel detalhado não altera a estrutura do layout subjacente. Portanto, o botão "Voltar" pode sair do app ou do gráfico de navegação atual porque não há mudança de layout para reverter no contexto atual. Em um layout de painel único, pressionar "Voltar" vai pular as mudanças de conteúdo na visualização de detalhes e retornar à visualização de lista, já que isso representa uma mudança clara de layout.

Confira estes exemplos:

  • Vários painéis:você está visualizando um e-mail (item 1) no painel de detalhes. Clicar em outro e-mail (item 2) atualiza o painel de detalhes, mas os painéis de lista e de detalhes permanecem visíveis. Pressionar "Voltar" pode sair do app ou do fluxo de navegação atual.
  • Painel único:você visualiza o item 1 e, em seguida, o item 2. Pressionar "Voltar" vai retornar diretamente ao painel de lista de e-mails.

Use isso quando quiser que os usuários percebam transições de layout distintas com cada ação de volta.

Mudança no valor da navegação.
PopUntilContentChange

Esse comportamento prioriza o conteúdo mostrado. Se você visualizar o item 1 e, em seguida, o item 2, pressionar "Voltar" vai reverter para o item 1, independentemente do layout.

Confira estes exemplos:

  • Vários painéis:você visualiza o item 1 no painel de detalhes e clica no item 2 na lista. O painel de detalhes é atualizado. Pressionar "Voltar" vai restaurar o painel de detalhes para o item 1.
  • Painel único:a mesma reversão de conteúdo ocorre.

Use isso quando o usuário espera retornar ao conteúdo visualizado anteriormente com a ação de volta.

a transição entre dois painéis de detalhes
PopUntilCurrentDestinationChange

Esse comportamento remove a backstack até que o destino de navegação atual mude. Isso se aplica igualmente a layouts de painel único e de vários painéis.

Confira estes exemplos:

Independentemente de você estar em um layout de painel único ou de vários painéis, pressionar "Voltar" sempre vai mover o foco do elemento de navegação destacado para o destino anterior. No nosso app de e-mail, isso significa que a indicação visual do painel selecionado será alterada.

Use isso quando manter uma indicação visual clara da navegação atual for crucial para a experiência do usuário.

navegar entre os painéis de detalhes e de lista
PopLatest

Essa opção remove apenas o destino mais recente da backstack. Use essa opção para navegação de retorno sem pular estados intermediários.

Depois de implementar essas etapas, o código vai ficar parecido com este:

NavigableListDetailPaneScaffold(
    navigator = navigator,
    listPane = {
        AnimatedPane {
            ListContent(
                words = sampleWords,
                selectionState = navigator.currentDestination?.contentKey?.let {
                    SelectionVisibilityState.ShowSelection(it)
                } ?: SelectionVisibilityState.NoSelection,
                onWordClick = { word ->
                    scope.launch {
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, word)
                    }
                },
                animatedVisibilityScope = this@AnimatedPane,
                sharedTransitionScope = this@SharedTransitionLayout
            )
        }
    },
    detailPane = {
        AnimatedPane {
            DetailContent(
                definedWord = navigator.currentDestination?.contentKey,
                animatedVisibilityScope = this@AnimatedPane,
                sharedTransitionScope = this@SharedTransitionLayout,
                onClosePane = {
                    scope.launch {
                        navigator.navigateBack(
                            backNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange
                        )

                    }
                }
            )
        }
    }