Crea un layout per i riquadri di supporto

Il layout del riquadro di supporto mantiene l'attenzione dell'utente sui contenuti principali dell'app, visualizzando al contempo informazioni di supporto pertinenti. Ad esempio, il riquadro principale potrebbe mostrare i dettagli di un film, mentre il riquadro di supporto elenca film simili, film dello stesso regista o opere con gli stessi attori.

Per maggiori dettagli, consulta le linee guida per il riquadro di supporto di Material 3.

Implementa un riquadro di supporto con uno scaffold

NavigableSupportingPaneScaffold è un componibile che semplifica l'implementazione di un layout del riquadro di supporto in Jetpack Compose. Aggrega SupportingPaneScaffold e aggiunge la navigazione integrata e la gestione della funzionalità Indietro predittivo.

Un'impalcatura del riquadro di supporto supporta fino a tre riquadri:

  • Riquadro principale: mostra i contenuti principali.
  • Riquadro di supporto: fornisce contesto o strumenti aggiuntivi correlati al riquadro principale.
  • Riquadro aggiuntivo (facoltativo): utilizzato per contenuti supplementari, se necessario.

Lo scaffold si adatta in base alle dimensioni della finestra:

  • Nelle finestre grandi, i riquadri principale e di supporto vengono visualizzati uno accanto all'altro.
  • Nelle finestre piccole, è visibile un solo riquadro alla volta, che cambia man mano che gli utenti navigano.

    Contenuti principali che occupano la maggior parte del display con contenuti di supporto accanto.
    Figura 1. Layout del riquadro di supporto.

Aggiungi dipendenze

NavigableSupportingPaneScaffold fa parte della libreria di layout adattivi di Material 3.

Aggiungi le seguenti tre dipendenze correlate al file build.gradle della tua app o del tuo modulo:

Kotlin

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

Alla moda

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • adaptive: componenti di base di basso livello come HingeInfo e Posture

  • adaptive-layout: layout adattivi come ListDetailPaneScaffold e SupportingPaneScaffold

  • adaptive-navigation: componibili per la navigazione all'interno e tra i riquadri, nonché layout adattivi che supportano la navigazione per impostazione predefinita, come NavigableListDetailPaneScaffold e NavigableSupportingPaneScaffold

Verifica che il progetto includa compose-material3-adaptive versione 1.1.0-beta1 o successive.

Attiva il gesto Indietro predittivo

Per attivare le animazioni di Indietro predittivo in Android 15 o versioni precedenti, devi attivare il supporto del gesto Indietro predittivo. Per attivare questa funzionalità, aggiungi android:enableOnBackInvokedCallback="true" al tag <application> o ai singoli tag <activity> all'interno del file AndroidManifest.xml.

Se la tua app ha come target Android 16 (livello API 36) o versioni successive, la funzionalità Indietro predittivo è attivata per impostazione predefinita.

Crea un navigatore

Nelle finestre piccole, viene visualizzato un solo riquadro alla volta, quindi utilizza un ThreePaneScaffoldNavigator per spostarti da un riquadro all'altro. Crea un'istanza del navigatore con rememberSupportingPaneScaffoldNavigator.

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

Passa il navigatore allo scaffold

Lo scaffold richiede un ThreePaneScaffoldNavigator, ovvero un'interfaccia che rappresenta lo stato dello scaffold, ThreePaneScaffoldValue e un PaneScaffoldDirective.

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = { /*...*/ },
    supportingPane = { /*...*/ },
)

Il riquadro principale e il riquadro di supporto sono elementi componibili che contengono i tuoi contenuti. Utilizza AnimatedPane per applicare le animazioni predefinite dei riquadri durante la navigazione. Utilizza il valore dello scaffold per verificare se il riquadro di supporto è nascosto. In questo caso, visualizza un pulsante che chiama navigateTo(SupportingPaneScaffoldRole.Supporting) per visualizzare il riquadro di supporto.

Per gli schermi di grandi dimensioni, utilizza il metodo ThreePaneScaffoldNavigator.navigateBack() per chiudere il riquadro di supporto, passando la costante BackNavigationBehavior.PopUntilScaffoldValueChange. La chiamata di questo metodo forza una ricomposizione di NavigableSupportingPaneScaffold. Durante la ricomposizione, controlla la proprietà ThreePaneScaffoldNavigator.currentDestination per determinare se mostrare il riquadro di supporto.

Ecco un'implementazione completa dello scaffold:

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()
val backNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        AnimatedPane(
            modifier = Modifier
                .safeContentPadding()
                .background(Color.Red)
        ) {
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) {
                Button(
                    modifier = Modifier
                        .wrapContentSize(),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting)
                        }
                    }
                ) {
                    Text("Show supporting pane")
                }
            } else {
                Text("Supporting pane is shown")
            }
        }
    },
    supportingPane = {
        AnimatedPane(modifier = Modifier.safeContentPadding()) {
            Column {
                // Allow users to dismiss the supporting pane. Use back navigation to
                // hide an expanded supporting pane.
                if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) {
                    // Material design principles promote the usage of a right-aligned
                    // close (X) button.
                    IconButton(
                        modifier =  Modifier.align(Alignment.End).padding(16.dp),
                        onClick = {
                            scope.launch {
                                scaffoldNavigator.navigateBack(backNavigationBehavior)
                            }
                        }
                    ) {
                        Icon(Icons.Default.Close, contentDescription = "Close")
                    }
                }
                Text("Supporting pane")
            }

        }
    }
)

Estrai gli elementi componibili dei riquadri

Estrai i singoli riquadri di un SupportingPaneScaffold nei rispettivi elementi componibili per renderli riutilizzabili e testabili. Utilizza ThreePaneScaffoldScope per accedere a AnimatedPane se vuoi le animazioni predefinite:

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.MainPane(
    shouldShowSupportingPaneButton: Boolean,
    onNavigateToSupportingPane: () -> Unit,
    modifier: Modifier = Modifier,
) {
    AnimatedPane(
        modifier = modifier.safeContentPadding()
    ) {
        // Main pane content
        if (shouldShowSupportingPaneButton) {
            Button(onClick = onNavigateToSupportingPane) {
                Text("Show supporting pane")
            }
        } else {
            Text("Supporting pane is shown")
        }
    }
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.SupportingPane(
    scaffoldNavigator: ThreePaneScaffoldNavigator<Any>,
    modifier: Modifier = Modifier,
    backNavigationBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange,
) {
    val scope = rememberCoroutineScope()
    AnimatedPane(modifier = Modifier.safeContentPadding()) {
        Column {
            // Allow users to dismiss the supporting pane. Use back navigation to
            // hide an expanded supporting pane.
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) {
                // Material design principles promote the usage of a right-aligned
                // close (X) button.
                IconButton(
                    modifier =  modifier.align(Alignment.End).padding(16.dp),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateBack(backNavigationBehavior)
                        }
                    }
                ) {
                    Icon(Icons.Default.Close, contentDescription = "Close")
                }
            }
            Text("Supporting pane")
        }

    }
}

L'estrazione dei riquadri in elementi componibili semplifica l'utilizzo di SupportingPaneScaffold (confronta quanto segue con l'implementazione completa dello scaffold nella sezione precedente):

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) },
)

Se hai bisogno di un maggiore controllo su aspetti specifici dello scaffold, valuta l'utilizzo di SupportingPaneScaffold anziché di NavigableSupportingPaneScaffold. Questo accetta un PaneScaffoldDirective e ThreePaneScaffoldValue o ThreePaneScaffoldState separatamente. Questa flessibilità ti consente di implementare una logica personalizzata per la spaziatura dei riquadri e di determinare quanti riquadri devono essere visualizzati contemporaneamente. Puoi anche attivare il supporto di Indietro predittivo aggiungendo ThreePaneScaffoldPredictiveBackHandler.

Aggiungi ThreePaneScaffoldPredictiveBackHandler

Collega il gestore di Indietro predittivo che accetta un'istanza di ScaffoldNavigator e specifica backBehavior. Determina il modo in cui le destinazioni vengono rimosse dal backstack durante la navigazione a ritroso. Poi passa scaffoldDirective e scaffoldState a SupportingPaneScaffold. Utilizza l'overload che accetta un ThreePaneScaffoldState, passando scaffoldNavigator.scaffoldState.

Definisci i riquadri principale e di supporto all'interno di SupportingPaneScaffold. Utilizza AnimatedPane per le animazioni predefinite dei riquadri.

Dopo aver implementato questi passaggi, il codice dovrebbe essere simile al seguente:

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

ThreePaneScaffoldPredictiveBackHandler(
    navigator = scaffoldNavigator,
    backBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange
)

SupportingPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) },
)