Criar um layout de detalhes e listas

Detalhes e listas é um padrão de interface que consiste em um layout de painel duplo, em que um painel apresenta uma lista de itens e o 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. O layout de detalhes e listas também pode ser usado 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 toda a tela.
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 preditiva.

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 mais contexto 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 adaptativa do Material 3.

Adicione as três dependências relacionadas abaixo ao arquivo build.gradle do seu 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'
  • adaptável: elementos básicos 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, além de 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 do compose-material3-adaptive ou mais recente.

Ativar o gesto de volta preditivo

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

Quando o app segmentar o Android 16 (nível 36 da API) ou uma versão mais recente, o gesto de voltar preditivo será ativado por padrão.

Uso básico

Implemente NavigableListDetailPaneScaffold da seguinte maneira:

  1. Use uma classe que represente o conteúdo selecionado. Use uma classe Parcelable para salvar e restaurar o item de lista selecionado. Use o plug-in kotlin-parcelize para gerar o código para você.
  2. Crie um ThreePaneScaffoldNavigator com rememberListDetailPaneScaffoldNavigator.

Esse navegador é usado para alternar entre os painéis de lista, detalhes e extra. Ao declarar um tipo genérico, o navegador também rastreia o estado do scaffold, ou seja, qual MyItem está sendo exibido. 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 o 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 exibido no painel. A propriedade contentKey é do mesmo tipo especificado na chamada original para que você possa acessar todos os dados que precisa mostrar.

  1. Se quiser, 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á modificar esse comportamento especificando outra opção de BackNavigationBehavior.

Opções de BackNavigationBehavior

A seção a seguir usa o exemplo de um app de e-mails 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 de detalhes não altera a estrutura de layout. 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 em lista, já que isso representa uma mudança clara de layout.

Veja estes exemplos:

  • Vários painéis:você está lendo 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. Ao pressionar "Voltar", você retorna diretamente ao painel da lista de e-mails.

Use essa opção quando quiser que os usuários percebam transições de layout distintas a cada ação de voltar.

Ilustração mostrando mudanças no valor de navegação em diferentes
              painéis de layout.
PopUntilContentChange

Esse comportamento prioriza o conteúdo exibido. Se você visualizar o Item 1 e depois o Item 2, pressionar "Voltar" vai retornar ao Item 1, independente do layout.

Veja estes exemplos:

  • Vários painéis:você vê o Item 1 no painel de detalhes e clica no Item 2 na lista. O painel de detalhes é atualizado. Se você pressionar "Voltar", o painel de detalhes vai voltar para o Item 1.
  • Painel único:ocorre a mesma reversão de conteúdo.

Use essa opção quando o usuário quiser retornar ao conteúdo visualizado anteriormente com a ação de voltar.

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.

Veja estes exemplos:

Independentemente de você estar em um layout de painel único ou múltiplo, pressionar "Voltar" sempre vai mover o foco do elemento de navegação destacado para o destino anterior. No nosso app de e-mails, isso significa que a indicação visual do painel selecionado vai mudar.

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

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
                        )

                    }
                }
            )
        }
    }