Crea app adattive con Jetpack Compose

1. Introduzione

In questo codelab imparerai a creare app adattive per smartphone, tablet e pieghevoli e in che modo migliorano la connettività con Jetpack Compose. Scoprirai anche le best practice per l'utilizzo dei componenti e dei temi di Material 3.

Prima di entrare nel dettaglio dell'argomento, è importante capire cosa intendiamo per adattabilità.

Adattabilità

L'UI dell'app deve essere reattiva in base alle dimensioni, agli orientamenti e ai fattori di forma della finestra. Il layout adattivo cambia in base allo spazio sullo schermo disponibile. Queste modifiche spaziano da semplici modifiche al layout per riempire lo spazio, scegliendo i rispettivi stili di navigazione, fino a modificare completamente i layout per sfruttare spazio aggiuntivo.

Per scoprire di più, dai un'occhiata a Progettazione adattiva.

In questo codelab, esplorerai come usare e come pensare all'adattabilità quando usi Jetpack Compose. Tu crei un'applicazione chiamata Reply, che ti mostra come implementare l'adattabilità per tutti i tipi di schermi e in che modo l'adattabilità e la connettività funzionano insieme per offrire agli utenti un'esperienza ottimale.

Cosa imparerai a fare

  • Come progettare un'app in modo che abbia come target tutte le dimensioni delle finestre con Jetpack Compose.
  • Come scegliere come target della tua app diversi pieghevoli.
  • Come usare diversi tipi di navigazione per migliorare la connettività e l'accessibilità.
  • Come utilizzare i componenti di Material 3 per offrire la migliore esperienza possibile su ogni dimensione di finestra.

Che cosa ti serve

Per questo codelab, utilizzerai l'emulatore ridimensionabile, che ti consente di passare da un tipo di dispositivo all'altro e da una dimensione di finestre all'altra.

Emulatore ridimensionabile con opzioni di smartphone, aperto, tablet e desktop.

Se non hai dimestichezza con Compose, valuta la possibilità di seguire il codelab sulle nozioni di base di Jetpack Compose prima di completare questo codelab.

Cosa creerai

  • Un'app client di posta elettronica interattiva che utilizza le best practice per un design adattabile, diverse navigazioni Material e un utilizzo ottimale dello spazio sullo schermo.

Presentazione del supporto di più dispositivi che raggiungerai in questo codelab

2. Configurazione

Per ottenere il codice per questo codelab, clona il repository GitHub dalla riga di comando:

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

In alternativa, puoi scaricare il repository come file ZIP:

Ti consigliamo di iniziare con il codice nel ramo principale e seguire la procedura passo passo del codelab secondo i tuoi tempi.

Apri progetto in Android Studio

  1. Nella finestra Ti diamo il benvenuto in Android Studio, seleziona c01826594f360d94.pngApri un progetto esistente.
  2. Seleziona la cartella <Download Location>/AdaptiveUiCodelab (assicurati di selezionare la directory AdaptiveUiCodelab che contiene build.gradle).
  3. Dopo che Android Studio ha importato il progetto, verifica di essere in grado di eseguire il ramo main.

Scopri il codice iniziale

Il codice filiale main contiene il pacchetto ui. All'interno del pacchetto dovrai utilizzare i seguenti file:

  • MainActivity.kt - Attività del punto di contatto da cui avvii l'app.
  • ReplyApp.kt: contiene gli elementi componibili dell'interfaccia utente della schermata principale.
  • ReplyHomeViewModel.kt: fornisce i dati e lo stato UI per i contenuti dell'app.
  • ReplyListContent.kt: contiene elementi componibili per fornire elenchi e schermate dei dettagli.

Innanzitutto, ti concentrerai su MainActivity.kt.

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )
        }
    }
}

Se esegui questa app su un emulatore ridimensionabile e provi diversi tipi di dispositivi, come uno smartphone o un tablet, la UI si espande nello spazio specificato, invece di sfruttare lo spazio dello schermo o offrire un'ergonomia della connettività.

Schermata iniziale sullo smartphone

Visualizzazione estesa iniziale sul tablet

Lo aggiornerai per sfruttare lo spazio sullo schermo, aumentare l'usabilità e migliorare l'esperienza utente complessiva.

3. Rendi adattabili le app

Questa sezione illustra cosa significa rendere le app adattabili e quali componenti offre Material 3 per semplificarlo. Inoltre, copre i tipi di schermi e stati che sceglierai come target, tra cui smartphone, tablet, tablet di grandi dimensioni e pieghevoli.

Inizierai illustrando le nozioni di base sulle dimensioni delle finestre, sulla postura dei piegamenti e sui diversi tipi di opzioni di navigazione. Poi puoi usare queste API nella tua app per renderla più adattiva.

Dimensioni finestre

I dispositivi Android sono disponibili in tutte le forme e dimensioni, dagli smartphone ai pieghevoli, ai tablet e ai dispositivi ChromeOS. Per supportare il maggior numero possibile di dimensioni di finestre, la tua UI deve essere reattiva e adattiva. Per aiutarti a trovare la soglia giusta per la modifica dell'interfaccia utente della tua app, abbiamo definito i valori dei punti di interruzione che consentono di classificare i dispositivi in classi di dimensioni predefinite (compatta, media ed espansa), chiamate classi di dimensioni della finestra. Si tratta di un insieme di punti di interruzione dell'area visibile guidati che ti aiutano a progettare, sviluppare e testare layout delle applicazioni adattabili e adattivi.

Le categorie sono state scelte in modo specifico per bilanciare la semplicità del layout e la flessibilità di ottimizzare la tua app per casi unici. La classe delle dimensioni della finestra è sempre determinata dallo spazio sullo schermo disponibile per l'app, che potrebbe non corrispondere all'intero schermo fisico per il multitasking o ad altre segmentazioni.

WindowwidthSizeClass per una larghezza compatta, media ed estesa.

WindowHeightSizeClass per altezza compatta, media ed estesa.

Sia la larghezza che l'altezza sono classificate separatamente, pertanto in qualsiasi momento l'app prevede due classi di dimensioni delle finestre: una per la larghezza e una per l'altezza. La larghezza disponibile è in genere più importante dell'altezza disponibile a causa dell'ubiquità dello scorrimento verticale, quindi in questo caso utilizzerai anche classi di dimensioni della larghezza.

Stati di piegatura

I dispositivi pieghevoli presentano ancora più situazioni a cui la tua app può adattarsi grazie alle loro dimensioni variabili e alla presenza di cerniere. Le cerniere possono oscurare parte del display, rendendo quell'area non adatta per mostrare contenuti; potrebbero anche essere separati, il che significa che ci sono due display fisici separati quando il dispositivo è aperto.

Postura pieghevole, piatta e semiaperta

Inoltre, l'utente potrebbe guardare il display interno mentre la cerniera è parzialmente aperta, il che genera posizioni fisiche diverse in base all'orientamento della piega: postura da tavolo (piega orizzontale, mostrata a destra nell'immagine sopra) e postura del libro (piega verticale).

Scopri di più su posture e cerniere piegate.

Devi considerare tutti questi aspetti quando implementi i layout adattivi che supportano i pieghevoli.

Ricevi informazioni adattive

La libreria Material3 adaptive offre un pratico accesso alle informazioni sulla finestra in cui è in esecuzione la tua app.

  1. Aggiungi voci per questo artefatto e la sua versione al file di catalogo delle versioni:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0-beta01"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. Nel file di build del modulo dell'app, aggiungi la nuova dipendenza della libreria ed esegui una sincronizzazione Gradle:

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

Ora, in qualsiasi ambito componibile, puoi utilizzare currentWindowAdaptiveInfo() per ottenere un oggetto WindowAdaptiveInfo contenente informazioni come l'attuale classe delle dimensioni della finestra e se il dispositivo è in una posizione pieghevole come la postura da tavolo.

Puoi provare subito questa funzionalità in MainActivity.

  1. In onCreate() all'interno del blocco ReplyTheme, ottieni le informazioni adattive alla finestra e visualizza le classi di dimensioni in un componibile Text (puoi aggiungerlo dopo l'elemento ReplyApp()):

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(20.dp)
            )
        }
    }
}

Se esegui l'app ora, vengono visualizzate le classi di dimensioni della finestra stampate sopra i contenuti dell'app. Scopri cos'altro è fornito nelle informazioni adattive della finestra. Dopodiché puoi rimuovere questo Text poiché copre i contenuti dell'app e non sarà necessario per i passaggi successivi.

4. Navigazione dinamica

Ora adatti la navigazione dell'app al cambiamento di stato e dimensioni del dispositivo per migliorare la connettività.

La raggiungibilità è la capacità di navigare o avviare un'interazione con un'app senza richiedere posizioni estreme della mano o cambiare i posizionamenti delle mani. Quando gli utenti tengono in mano lo smartphone, solitamente le loro dita si trovano nella parte inferiore dello schermo. Quando gli utenti tengono in mano un dispositivo pieghevole o un tablet aperto, solitamente le loro dita sono vicine ai lati. Mentre progetti la tua app e decidi dove posizionare gli elementi di interfaccia utente interattivi nel layout, considera le implicazioni ergonomiche delle diverse aree dello schermo.

  • Quali aree puoi raggiungere comodamente quando tieni il dispositivo?
  • Quali aree possono essere raggiunte soltanto allungando le dita, il che potrebbe essere scomodo?
  • Quali aree sono difficili da raggiungere o sono lontane da dove l'utente tiene il dispositivo?

La navigazione è la prima cosa con cui gli utenti interagiscono e contiene azioni di importanza elevata relative ai percorsi critici degli utenti, pertanto dovrebbe essere collocata nelle aree più facili da raggiungere. Il Material include diversi componenti che ti aiutano a implementare la navigazione, a seconda della classe delle dimensioni della finestra del dispositivo.

Navigazione in basso

La navigazione nella parte inferiore è perfetta per le dimensioni compatte, in quanto tieni il dispositivo in modo naturale sul punto in cui il pollice può raggiungere facilmente tutti i punti di contatto della navigazione in basso. Utilizzalo ogni volta che hai un dispositivo di dimensioni compatte o un dispositivo pieghevole in uno stato pieghevole compatto.

Barra di navigazione in basso con elementi

Per una finestra di larghezza media, la barra di navigazione è ideale per la connettività perché il pollice cade naturalmente lungo il lato del dispositivo. Puoi anche combinare una barra di navigazione con un riquadro di navigazione a scomparsa per visualizzare ulteriori informazioni.

Ferrovia di navigazione con elementi

Il riquadro di navigazione a scomparsa offre un modo semplice per visualizzare informazioni dettagliate sulle schede di navigazione ed è facilmente accessibile quando utilizzi tablet o dispositivi più grandi. Sono disponibili due tipi di riquadri di navigazione a scomparsa: un riquadro di navigazione modale e un riquadro di navigazione permanente.

Riquadro di navigazione a scomparsa

Puoi utilizzare un riquadro a scomparsa di navigazione modale per telefoni e tablet di dimensioni medie e compatte, che può essere espanso o nascosto come overlay sui contenuti. A volte può essere combinata con una barra di navigazione.

Riquadro di navigazione modale a scomparsa con elementi

Riquadro di navigazione a scomparsa permanente

Su tablet, Chromebook e computer di grandi dimensioni, puoi utilizzare un riquadro di navigazione a scomparsa permanente per la navigazione fissa.

Riquadro di navigazione a scomparsa permanente con elementi

Implementare la navigazione dinamica

Ora passerai da un tipo di navigazione all'altro man mano che lo stato e le dimensioni del dispositivo cambiano.

Al momento, l'app mostra sempre un NavigationBar sotto i contenuti dello schermo, indipendentemente dallo stato del dispositivo. In alternativa, puoi utilizzare il componente Materiale NavigationSuiteScaffold per passare automaticamente da un componente di navigazione all'altro in base a informazioni come l'attuale classe delle dimensioni della finestra.

  1. Aggiungi la dipendenza Gradle per ottenere questo componente aggiornando il catalogo delle versioni e lo script di build dell'app, quindi esegui una sincronizzazione Gradle:

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0-beta01"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. Trova la funzione componibile ReplyNavigationWrapper() in ReplyApp.kt e sostituisci Column e i suoi contenuti con un NavigationSuiteScaffold:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

L'argomento navigationSuiteItems è un blocco che ti consente di aggiungere elementi utilizzando la funzione item(), in modo simile all'aggiunta di elementi in un LazyColumn. All'interno del lambda finale, questo codice chiama content() passato come argomento a ReplyNavigationWrapperUI().

Esegui l'app sull'emulatore e prova a cambiare le dimensioni tra smartphone, pieghevole e tablet: la barra di navigazione si trasformerà in una barra di navigazione e viceversa.

Su finestre molto ampie, ad esempio su un tablet in orizzontale, potresti voler mostrare il riquadro di navigazione a scomparsa permanente. NavigationSuiteScaffold non supporta la visualizzazione di un riquadro a scomparsa permanente, anche se non è presente in nessuno dei valori WindowWidthSizeClass correnti. Tuttavia, puoi farlo con una piccola modifica.

  1. Aggiungi il seguente codice poco prima della chiamata al numero NavigationSuiteScaffold:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

Questo codice prima ottiene le dimensioni della finestra e le converte in unità DP utilizzando currentWindowSize() e LocalDensity.current, quindi confronta la larghezza della finestra per decidere il tipo di layout dell'interfaccia utente di navigazione. Se la larghezza della finestra è almeno 1200.dp, viene utilizzato NavigationSuiteType.NavigationDrawer. In caso contrario, torna al calcolo predefinito.

Quando esegui di nuovo l'app sull'emulatore ridimensionabile e provi diversi tipi, ogni volta che la configurazione dello schermo cambia o apri un dispositivo pieghevole, la navigazione cambia per il tipo appropriato per quella dimensione.

Vengono mostrate le variazioni dell&#39;adattabilità per dispositivi di dimensioni diverse.

Congratulazioni. Ora conosci i diversi tipi di navigazione per supportare differenti dimensioni e stati delle finestre.

Nella sezione successiva, scoprirai come sfruttare l'area rimanente della schermata anziché estendere lo stesso elemento dell'elenco da un lato all'altro.

5. Utilizzo dello spazio sullo schermo

Non importa se esegui l'app su un tablet di piccole dimensioni, di un dispositivo aperto o di un tablet di grandi dimensioni, lo schermo viene esteso per riempire lo spazio rimanente. Devi assicurarti di poter sfruttare lo spazio sullo schermo per mostrare maggiori informazioni, ad esempio relative all'app, e mostrare email e thread agli utenti nella stessa pagina.

Material 3 definisce tre layout canonici, ognuno dei quali ha una configurazione per classi di dimensioni delle finestre compatte, medie ed espanse. Il layout canonico Dettagli elenco è perfetto per questo caso d'uso ed è disponibile in modalità di scrittura come ListDetailPaneScaffold.

  1. Per ottenere questo componente, aggiungi le seguenti dipendenze ed esegui una sincronizzazione Gradle:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0-beta01"

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. Trova la funzione componibile ReplyAppContent() in ReplyApp.kt, che al momento mostra solo il riquadro dell'elenco chiamando ReplyListPane(). Sostituisci questa implementazione con ListDetailPaneScaffold inserendo il codice seguente. Poiché questa è un'API sperimentale, aggiungerai anche l'annotazione @OptIn alla funzione ReplyAppContent():

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

Questo codice crea prima un navigatore utilizzando rememberListDetailPaneNavigator(). Il navigatore ti consente di controllare quale riquadro viene visualizzato e quali contenuti devono essere rappresentati al suo interno, che verranno mostrati in seguito.

ListDetailPaneScaffold mostrerà due riquadri quando la classe delle dimensioni della larghezza della finestra viene espansa. In caso contrario, verrà visualizzato un riquadro o l'altro in base ai valori forniti per due parametri: la direttiva scaffold e il valore dello scaffold. Per ottenere il comportamento predefinito, questo codice utilizza la direttiva scaffold e il valore scaffold fornito dal navigatore.

I restanti parametri obbligatori sono lambda componibili per i riquadri. ReplyListPane() e ReplyDetailPane() (disponibili in ReplyListContent.kt) vengono utilizzati per riempire i ruoli rispettivamente dell'elenco e dei riquadri dei dettagli. ReplyDetailPane() prevede un argomento email, quindi per ora questo codice utilizza la prima email dell'elenco di email in ReplyHomeUIState.

Esegui l'app e imposta la visualizzazione dell'emulatore su pieghevole o tablet (potrebbe essere necessario modificare l'orientamento) per vedere il layout a due riquadri. È già molto meglio di prima.

Ora esaminiamo alcuni dei comportamenti desiderati per questa schermata. Quando un utente tocca un'email nel riquadro dell'elenco, questa dovrebbe apparire nel riquadro dei dettagli insieme a tutte le risposte. Al momento l'app non tiene traccia dell'email selezionata e toccare un elemento non produce alcun effetto. Il posto migliore in cui conservare queste informazioni è il resto dello stato dell'interfaccia utente in ReplyHomeUIState.

  1. Apri ReplyHomeViewModel.kt e trova la classe di dati ReplyHomeUIState. Aggiungi una proprietà per l'email selezionata, con il valore predefinito null:

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. Nello stesso file, ReplyHomeViewModel ha una funzione setSelectedEmail() che viene chiamata quando l'utente tocca una voce dell'elenco. Modifica questa funzione per copiare lo stato dell'interfaccia utente e registrare l'email selezionata:

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

Aspetti da considerare è cosa succede prima che l'utente abbia toccato un elemento e l'indirizzo email selezionato è null. Cosa dovrebbe essere visualizzato nel riquadro dei dettagli? Esistono diversi modi per gestire questo caso, ad esempio mostrare il primo elemento dell'elenco per impostazione predefinita.

  1. Nello stesso file, modifica la funzione observeEmails(). Quando l'elenco di email è stato caricato, se per lo stato precedente dell'interfaccia utente non era stata selezionata un'email, impostala sul primo elemento:

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. Torna a ReplyApp.kt e utilizza l'email selezionata, se disponibile, per compilare i contenuti del riquadro dei dettagli:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

Esegui di nuovo l'app e imposta l'emulatore sulle dimensioni del tablet; noterai che, toccando una voce dell'elenco, si aggiorna il contenuto del riquadro dei dettagli.

Questa operazione funziona molto bene quando entrambi i riquadri sono visibili, ma quando la finestra ha spazio per mostrarne solo uno, sembra che quando tocchi un elemento non succede nulla. Prova a passare dalla visualizzazione dell'emulatore a uno smartphone o a un dispositivo pieghevole in verticale e nota che è visibile solo il riquadro elenco anche dopo aver toccato un elemento. Questo perché, anche se l'email selezionata viene aggiornata, ListDetailPaneScaffold mantiene lo stato attivo sul riquadro dell'elenco in queste configurazioni.

  1. Per risolvere il problema, inserisci il seguente codice come lambda passato a ReplyListPane:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

Questo lambda utilizza il navigatore creato in precedenza per aggiungere un ulteriore comportamento quando viene fatto clic su un elemento. Chiamerà il lambda originale passato a questa funzione, quindi chiama anche navigator.navigateTo() specificando quale riquadro deve essere mostrato. A ogni riquadro dello scaffold è associato un ruolo, che per il riquadro dei dettagli è ListDetailPaneScaffoldRole.Detail. Nelle finestre più piccole, sembrerà che l'app abbia fatto la navigazione in avanti.

L'app deve anche gestire ciò che accade quando l'utente preme il pulsante Indietro dal riquadro dei dettagli e questo comportamento sarà diverso a seconda che siano visibili uno o due riquadri.

  1. Supporta la navigazione a ritroso aggiungendo il codice seguente.

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

Il navigatore conosce lo stato completo di ListDetailPaneScaffold, se è possibile la navigazione a ritroso e cosa fare in tutti questi scenari. Questo codice crea un BackHandler che viene attivato ogni volta che il navigatore può tornare indietro e all'interno delle chiamate lambda navigateBack(). Inoltre, per rendere molto più fluida la transizione tra i riquadri, ogni riquadro è aggregato in un componibile AnimatedPane().

Esegui di nuovo l'app su un emulatore ridimensionabile per tutti i diversi tipi di dispositivi e nota che ogni volta che la configurazione dello schermo cambia o quando apri un dispositivo pieghevole, i contenuti di navigazione e dello schermo cambiano dinamicamente in risposta ai cambiamenti di stato del dispositivo. Prova anche a toccare le email nel riquadro dell'elenco per vedere come si comporta il layout su schermi diversi, mostrando entrambi i riquadri affiancati o animandoli in modo fluido.

Vengono mostrate le variazioni dell&#39;adattabilità per dispositivi di dimensioni diverse.

Congratulazioni, hai reso la tua app adattabile a tutti i tipi di stati e dimensioni dei dispositivi. Esegui l'app su pieghevoli, tablet o altri dispositivi mobili.

6. Complimenti

Complimenti! Hai completato questo codelab e hai imparato a rendere le app adattive con Jetpack Compose.

Hai imparato a controllare le dimensioni e lo stato di piegatura di un dispositivo e ad aggiornare di conseguenza l'UI, la navigazione e altre funzioni della tua app. Hai anche imparato come l'adattabilità migliora la connettività e l'esperienza utente.

Passaggi successivi

Dai un'occhiata agli altri codelab sul percorso di scrittura.

App di esempio

  • Gli esempi di composizione sono una raccolta di molte app che incorporano le best practice spiegate nei codelab.

Documenti di riferimento