Supporta schermi di dimensioni diverse

Il supporto di schermi di dimensioni diverse consente di accedere alla tua app da parte della più ampia gamma di dispositivi e del maggior numero di utenti.

Per supportare il maggior numero possibile di dimensioni di schermo, progetta i layout delle app in modo che siano reattivi e adattivi. I layout reattivi/adattivi offrono un'esperienza utente ottimizzata indipendentemente dalle dimensioni dello schermo, consentendo all'app di ospitare telefoni, tablet, pieghevoli, dispositivi ChromeOS, orientamento verticale e orizzontale e configurazioni ridimensionabili come la modalità multi-finestra.

I layout adattabili/adattivi cambiano in base allo spazio di visualizzazione disponibile. Le modifiche vanno da piccole modifiche al layout che riempiono lo spazio (design reattivo) alla sostituzione completa di un layout con un altro in modo che l'app possa adattarsi al meglio alle diverse dimensioni del display (design adattivo).

In quanto toolkit dichiarativo dell'interfaccia utente, Jetpack Compose è ideale per progettare e implementare layout che cambiano dinamicamente per visualizzare i contenuti in modo diverso in diverse dimensioni.

Apporta modifiche di grandi dimensioni al layout per elementi componibili a livello di schermo espliciti

Quando utilizzi Compose per disporre un'intera applicazione, i componibili a livello di app e di schermo occupano tutto lo spazio concesso all'app per il rendering. A questo livello della progettazione, potrebbe essere utile modificare il layout complessivo di uno schermo per sfruttare schermi più grandi.

Evita di utilizzare valori hardware fisici per prendere decisioni relative al layout. Potresti avere la tentazione di prendere decisioni basate su un valore tangibile fisso (il dispositivo è un tablet? Lo schermo fisico ha un determinato formato?), ma le risposte a queste domande potrebbero non essere utili per determinare lo spazio con cui può lavorare la tua UI.

Un diagramma che mostra diversi fattori di forma dei dispositivi, tra cui smartphone, pieghevole, tablet e laptop.
Figura 1. Fattori di forma di smartphone, pieghevoli, tablet e laptop

Sui tablet, un'app potrebbe essere in esecuzione in modalità multi-finestra, il che significa che l'app potrebbe dividere lo schermo con un'altra app. Su ChromeOS, un'app potrebbe essere in una finestra ridimensionabile. Potrebbero esserci anche più schermi fisici, ad esempio un dispositivo pieghevole. In tutti questi casi, le dimensioni fisiche dello schermo non sono rilevanti per decidere come visualizzare i contenuti.

Dovresti invece prendere decisioni in base alla parte effettiva dello schermo assegnata alla tua app, ad esempio le metriche della finestra corrente fornite dalla libreria WindowManager di Jetpack. Per scoprire come utilizzare WindowManager in un'app Compose, guarda l'esempio di JetNews.

Questo approccio rende la tua app più flessibile, in quanto si comporta bene in tutti gli scenari descritti in precedenza. rendendo i layout adatti allo spazio dello schermo disponibile riduce anche la quantità di gestione speciale per supportare piattaforme come ChromeOS e fattori di forma come tablet e pieghevoli.

Una volta osservato lo spazio pertinente disponibile per la tua app, è utile convertire le dimensioni non elaborate in una classe di dimensioni significativa, come descritto in Classi di dimensioni delle finestre. Questo raggruppa le dimensioni in bucket di dimensioni standard, ovvero punti di interruzione progettati per bilanciare la semplicità e la flessibilità per ottimizzare l'app per la maggior parte dei casi unici. Queste classi di dimensioni si riferiscono alla finestra complessiva della tua app, quindi usa queste classi per prendere decisioni relative al layout che influiscono sul layout complessivo dello schermo. Puoi passare queste classi di dimensioni come stato oppure puoi eseguire un'ulteriore logica per creare uno stato derivato da trasmettere ai componibili nidificati.

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Questo approccio a più livelli circoscrive la logica relativa alle dimensioni dello schermo in un'unica posizione, invece di disperderla nell'app in molti punti che devono essere sincronizzati. Questa singola posizione produce lo stato, che può essere trasmesso esplicitamente ad altri componibili, proprio come faresti per qualsiasi altro stato dell'app. Il passaggio esplicito dello stato semplifica i singoli elementi componibili, poiché si tratta di normali funzioni componibili che accettano la classe di dimensioni o la configurazione specificata insieme ad altri dati.

Gli elementi componibili nidificati flessibili sono riutilizzabili

I componibili sono più riutilizzabili quando possono essere posizionati in un'ampia varietà di luoghi. Se un componibile presuppone che venga sempre posizionato in una determinata posizione con una dimensione specifica, sarà più difficile riutilizzarlo altrove in un'altra posizione o con una diversa quantità di spazio disponibile. Ciò significa anche che singoli elementi componibili riutilizzabili devono evitare implicitamente di dipendere implicitamente da informazioni sulle dimensioni "globali".

Considera l'esempio seguente: immagina un componibile nidificato che implementa un layout elenco-dettagli, che può mostrare uno o due riquadri affiancati.

Screenshot di un'app che mostra due riquadri uno accanto all'altro.
Figura 2. Screenshot di un'app che mostra un tipico layout elenco-dettagli: 1 è l'area dell'elenco; 2 l'area dei dettagli.

Vogliamo che questa decisione faccia parte del layout complessivo dell'app, quindi passiamo la decisione da un componibile a livello di schermo, come abbiamo visto sopra:

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

E se invece volessimo che un componibile modifichi in modo indipendente il suo layout in base allo spazio disponibile? Ad esempio, una scheda che vuole mostrare ulteriori dettagli se lo spazio lo consente. Vogliamo eseguire una logica basata su alcune dimensioni disponibili, ma quale dimensione nello specifico?

Esempi di due schede diverse.
Figura 3. Scheda stretta che mostra solo un'icona e un titolo e una scheda più ampia con l'icona, il titolo e la descrizione breve.

Come abbiamo visto prima, è meglio evitare di utilizzare le dimensioni effettive dello schermo del dispositivo. Questo non sarà preciso per più schermi né se l'app non è a schermo intero.

Poiché l'elemento componibile non è componibile a livello di schermo, non è necessario utilizzare direttamente le attuali metriche della finestra per massimizzare la riusabilità. Se il componente viene inserito con spaziatura interna (ad esempio per riquadri) o se sono presenti componenti come barre di navigazione o barre delle app, la quantità di spazio disponibile per il componibile può essere notevolmente diversa da quella complessiva disponibile per l'app.

Pertanto, dobbiamo utilizzare la larghezza assegnata al componibile per visualizzarlo. Abbiamo due opzioni per ottenere questa larghezza:

Se vuoi modificare dove o come vengono visualizzati i contenuti, puoi utilizzare una raccolta di modificatori o un layout personalizzato per rendere il layout adattabile. Può essere semplice, ad esempio chiedere a un figlio di riempire tutto lo spazio disponibile o distendere gli elementi secondari con più colonne se c'è abbastanza spazio.

Se vuoi cambiare cosa mostri, puoi utilizzare BoxWithConstraints come alternativa più efficace. Questo componibile fornisce vincoli di misurazione che puoi utilizzare per chiamare diversi componibili in base allo spazio disponibile. Tuttavia, ciò ha delle spese, in quanto BoxWithConstraints rimanda la composizione fino alla fase del layout, quando questi vincoli sono noti, con conseguente aumento dell'attività da svolgere durante il layout.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

Assicurati che tutti i dati siano disponibili per dimensioni diverse

Quando sfrutti uno schermo aggiuntivo, su uno schermo grande potresti avere più spazio per mostrare all'utente più contenuti rispetto a uno schermo piccolo. Durante l'implementazione di un componibile con questo comportamento, si potrebbe avere la tentazione di essere efficiente e caricare i dati come effetto collaterale della dimensione attuale.

Tuttavia, ciò va in contrasto con i principi del flusso di dati unidirezionale, in cui i dati possono essere sollevati e forniti ai componenti componibili affinché vengano visualizzati in modo appropriato. Devono essere forniti al componibile un volume sufficiente di dati, in modo che quest'ultimo abbia sempre ciò che deve visualizzare su qualsiasi dimensione, anche se una parte dei dati potrebbe non essere sempre utilizzata.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Basandoci sull'esempio Card, tieni presente che passiamo sempre description all'Card. Anche se description viene utilizzato solo quando la larghezza lo consente, è sempre necessario in Card, indipendentemente dalla larghezza disponibile.

La trasmissione costante dei dati semplifica i layout adattivi rendendoli meno stateful ed evita l'attivazione di effetti collaterali quando si passa da una dimensione all'altra (il che può verificarsi a causa del ridimensionamento di una finestra, del cambiamento dell'orientamento o della chiusura e apertura di un dispositivo).

Questo principio consente anche di mantenere lo stato in tutte le modifiche al layout. Raccogliendo le informazioni che potrebbero non essere utilizzate in tutte le dimensioni, è possibile preservare lo stato dell'utente quando cambiano le dimensioni del layout. Ad esempio, possiamo istruire un flag booleano showMore in modo che lo stato dell'utente venga mantenuto quando i ridimensionamenti comportano l'occultamento della descrizione nel layout:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

Scopri di più

Per saperne di più sui layout personalizzati in Compose, consulta le seguenti risorse aggiuntive.

App di esempio

  • I layout canonici per schermi di grandi dimensioni sono un repository di pattern di progettazione comprovati che offrono un'esperienza utente ottimale su dispositivi con schermi grandi
  • JetNews mostra come progettare un'app che adatti la sua UI per utilizzare lo spazio disponibile
  • Rispondi è un campione adattivo per supportare dispositivi mobili, tablet e pieghevoli.
  • Ora disponibile in Android: è un'app che usa layout adattivi per supportare schermi di dimensioni diverse

Video