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 responsivi. 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 qualità di toolkit UI dichiarativo, Jetpack Compose è ideale per progettare e implementare layout che cambiano dinamicamente per visualizzare i contenuti in modo diverso su una serie di dimensioni del display.
Specificare le modifiche di layout di grandi dimensioni per i componenti composibili a livello di schermata
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. Potrebbe essere temptinge prendere decisioni in base a 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.
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 trovarsi 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, dai un'occhiata all'esempio JetNews.
Seguire questo approccio rende la tua app più flessibile, in quanto si comporterà bene in tutti gli scenari precedenti. 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 della tua app, quindi utilizzale per le decisioni di layout che influiscono 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 genera uno stato che può essere trasmesso esplicitamente ad altri composabili, 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.
I composabili 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à diversa di spazio disponibile. Ciò significa anche che i singoli componenti 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.
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 in modo indipendente 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 in base a una dimensione disponibile, ma quale dimensione nello specifico?
Come abbiamo visto sopra, dobbiamo evitare di utilizzare le dimensioni dello schermo reale del dispositivo. I risultati non saranno accurati su più schermi e nemmeno se l'app non è a schermo intero.
Poiché il composable non è un composable a livello di schermata, non dobbiamo nemmeno utilizzare direttamente le metriche della finestra corrente per massimizzare la riutilizzabilità. Se il componente viene posizionato con spaziatura interna (ad esempio per gli inserti) o se sono presenti componenti come barre di navigazione o barre app, la quantità di spazio disponibile per il composable 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 occupino 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 composable fornisce limiti di misura che puoi utilizzare per chiamare diversi composabili 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. Al composable devono essere forniti dati sufficienti in modo che abbia sempre a disposizione ciò che deve essere visualizzato in qualsiasi dimensione, anche se 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 (cosa che può 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 man mano che 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 sample adattabile per il supporto di dispositivi mobili, tablet e pieghevoli
- Ora su Android è un'app che utilizza layout adattivi per supportare diverse dimensioni dello schermo
Video
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Mappare i componenti al codice esistente
- Nozioni di base sul layout di composizione
- Fasi di Jetpack Compose