Supporta schermi di dimensioni diverse

Il supporto di diverse dimensioni dello schermo consente di accedere alla tua app alla più ampia gamma di dispositivi e al maggior numero di utenti.

Per supportare il maggior numero possibile di dimensioni dello schermo, progetta i layout delle app in modo che siano adattabili e reattivi. I layout adattabili/responsive offrono un'esperienza utente ottimizzata indipendentemente dalle dimensioni dello schermo, consentendo alla tua app di adattarsi a smartphone, tablet, dispositivi pieghevoli, dispositivi ChromeOS, orientamenti ritratto e paesaggio e configurazioni ridimensionabili come la modalità multi-finestra.

I layout adattabili/reattivi cambiano in base allo spazio di visualizzazione disponibile. Le modifiche vanno da piccoli aggiustamenti del layout che riempiono lo spazio (responsive design) alla sostituzione completa di un layout con un altro in modo che l'app possa adattarsi al meglio a dimensioni diverse 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 creare il layout di un'intera applicazione, i composabili a livello di app e di schermata occupano tutto lo spazio a disposizione per il rendering dell'app. A questo livello nel design, potrebbe essere opportuno modificare il layout complessivo di una schermata per usufruire di schermi più grandi.

Evita di utilizzare valori fisici e hardware per prendere decisioni sul 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ò funzionare l'interfaccia utente.

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à multifinestra, il che significa che potrebbe dividere lo schermo con un'altra app. Su ChromeOS, un'app potrebbe essere in una finestra ridimensionabile. Potrebbe anche essere presente più di uno schermo fisico, ad esempio con un dispositivo pieghevole. In tutti questi casi, le dimensioni fisiche dello schermo non sono pertinenti per decidere come visualizzare i contenuti.

Devi invece prendere decisioni in base alla parte effettiva dello schermo assegnata alla tua app, ad esempio le metriche della finestra correnti fornite dalla raccolta 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. Se rendi i tuoi layout adattabili allo spazio sullo schermo a loro disposizione, riduci anche la quantità di gestione speciale per supportare piattaforme come ChromeOS e fattori di forma come tablet e dispositivi 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 Utilizzare le classi di dimensioni della finestra. In questo modo, le dimensioni vengono raggruppate in bucket di dimensioni standard, ovvero breakpoint progettati per bilanciare la semplicità con la flessibilità necessaria per ottimizzare l'app per la maggior parte dei casi unici. Queste classi di dimensioni si riferiscono alla finestra complessiva dell'app, quindi usa queste classi per prendere decisioni relative al layout che incidono sul layout complessivo dello schermo. Puoi trasmettere queste classi di dimensioni come stato oppure eseguire una logica aggiuntiva per creare uno stato derivato da trasmettere ai composabili nidificati.

@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 limita la logica delle dimensioni dello schermo a una singola posizione, anziché disseminarla nell'app in molti punti che devono essere mantenuti 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 composabili, poiché saranno solo funzioni composabili normali che prendono la classe di dimensioni o la configurazione specificata insieme ad altri dati.

Gli elementi componibili nidificati flessibili sono riutilizzabili

I composabili sono più riutilizzabili se possono essere posizionati in una vasta gamma di luoghi. Se un composable presuppone che verrà sempre posizionato in una determinata posizione con dimensioni specifiche, sarà più difficile riutilizzarlo altrove in una posizione diversa o con una quantità di spazio diversa a disposizione. Ciò significa anche che i singoli componenti composable riutilizzabili devono evitare di dipendere implicitamente dalle informazioni sulle dimensioni "globali".

Prendi in considerazione il seguente esempio: immagina un composable nidificato che implementa un layout di elenco con dettagli, che può mostrare un riquadro o due riquadri affiancati.

Screenshot di un'app che mostra due riquadri affiancati.
Figura 2. Screenshot di un'app che mostra un layout di elenco e dettagli tipico: 1 è l'area dell'elenco; 2, l'area dei dettagli.

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

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

E se invece volessimo che un composable modificasse autonomamente il 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. Una scheda stretta che mostra solo un'icona e un titolo e una scheda più larga che mostra l'icona, il titolo e una breve descrizione.

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 i riquadri) o se sono presenti componenti come barre di navigazione o barre delle app, la quantità di spazio disponibile per il componibile può differire notevolmente dallo spazio complessivo disponibile per l'app.

Pertanto, dobbiamo utilizzare la larghezza effettivamente assegnata al composable per il rendering. 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. Potrebbe essere sufficiente che alcuni elementi figlio riempiano tutto lo spazio disponibile o disporre gli elementi figlio in più colonne se lo spazio è sufficiente.

Se vuoi modificare cosa mostrare, 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, questo ha un costo, poiché BoxWithConstraints rimanda la composizione alla fase di layout, quando questi vincoli sono noti, il che comporta un maggiore lavoro 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

Se utilizzi uno spazio sullo schermo aggiuntivo, su uno schermo grande potresti avere spazio per mostrare all'utente più contenuti rispetto a uno schermo piccolo. Quando si implementa un composable con questo comportamento, potrebbe essere allettante essere efficienti e caricare i dati come effetto collaterale delle dimensioni correnti.

Tuttavia, ciò va contro i principi del flusso di dati unidirezionale, in cui i dati possono essere sollevati e forniti ai composable per il rendering appropriato. Devono essere forniti al componibile un volume sufficiente di dati, in modo che quest'ultimo abbia sempre ciò che deve visualizzare in 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)
            }
        }
    }
}

Nell'esempio Card, tieni presente che passiamo sempre il description al Card. Anche se description viene utilizzato solo quando la larghezza lo consente, Card lo richiede sempre, indipendentemente dalla larghezza disponibile.

Il passaggio costante dei dati semplifica i layout adattabili rendendoli meno basati sugli stati e consente di evitare di attivare effetti collaterali quando si passa da una dimensione all'altra (che possono verificarsi a causa di una modifica delle dimensioni della finestra, della rotazione o del ripiegamento e dell'apertura di un dispositivo).

Questo principio consente inoltre di preservare lo stato durante le modifiche del layout. Se estraiamo le informazioni che potrebbero non essere utilizzate per tutte le dimensioni, possiamo preservare lo stato dell'utente quando le dimensioni del layout cambiano. Ad esempio, possiamo impostare un flag booleano showMore in modo che lo stato dell'utente venga mantenuto quando le modifiche delle dimensioni fanno sì che il layout alternato tra la visualizzazione e la scomparsa della descrizione:

@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 scoprire di più sui layout personalizzati in Scrivi, consulta le seguenti risorse aggiuntive.

App di esempio

  • CanonicalLayouts è un repository di pattern di progettazione comprovati che offrono un'esperienza utente ottimale su dispositivi con schermi di grandi dimensioni
  • JetNews mostra come progettare un'app che adatti la propria interfaccia utente per utilizzare lo spazio disponibile
  • Rispondi è un campione adattivo per supportare dispositivi mobili, tablet e pieghevoli.
  • Ora su Android è un'app che utilizza layout adattivi per supportare diverse dimensioni dello schermo

Video