1. Introduzione
L'ecosistema dei dispositivi Android è in continua evoluzione. Dalle prime tastiere hardware integrate al panorama moderno di dispositivi pieghevoli, tablet e finestre ridimensionabili in formato libero, le app per Android non sono mai state eseguite su una gamma di dispositivi così diversificata come oggi.
Sebbene si tratti di un'ottima notizia per gli sviluppatori, sono necessarie alcune ottimizzazioni delle app per soddisfare le aspettative di usabilità e garantire un'esperienza utente eccellente su diverse dimensioni dello schermo. Invece di scegliere come target ogni nuovo dispositivo uno alla volta, un'interfaccia utente reattiva/adattiva e un'architettura resiliente possono contribuire a rendere la tua app perfetta ovunque si trovino i tuoi utenti attuali e futuri, su dispositivi di qualsiasi dimensione e forma.
L'introduzione di ambienti Android ridimensionabili in formato libero è un ottimo modo per testare la tua UI reattiva/adattiva e prepararla per qualsiasi dispositivo. Questo codelab ti guiderà nella comprensione delle implicazioni del ridimensionamento e nell'implementazione di alcune best practice per rendere il ridimensionamento di un'app robusto e semplice.
Cosa creerai
Esplorerai le implicazioni del ridimensionamento in formato libero e ottimizzerai un'app per Android per dimostrare le best practice per il ridimensionamento. La tua app sarà in grado di:
Avere un manifest compatibile
- Rimuovere le limitazioni che impediscono il ridimensionamento libero di un'app
Mantenere lo stato quando viene ridimensionato
- Mantiene lo stato della UI quando viene ridimensionata utilizzando rememberSaveable
- Evita di duplicare inutilmente il lavoro in background per inizializzare la UI
Che cosa ti serve
- Conoscenza della creazione di applicazioni Android di base
- Conoscenza di ViewModel e State in Compose
- Un dispositivo di test che supporta il ridimensionamento della finestra in formato libero, ad esempio uno dei seguenti:
- Un Chromebook con configurazione ADB
- Un tablet che supporta la modalità Samsung DeX o la modalità Produttività
- L'emulatore Desktop Android Virtual Device in Android Studio
Se riscontri problemi (bug del codice, errori grammaticali, formulazione poco chiara e così via) mentre segui questo codelab, segnalali tramite il link Segnala un errore nell'angolo in basso a sinistra del codelab.
2. Per iniziare
Clona il repository da GitHub.
git clone https://github.com/android/large-screen-codelabs/
… oppure scarica un file ZIP del repository ed estrailo.
Importa progetto
- Apri Android Studio
- Scegli Importa progetto o File->Nuovo->Importa progetto.
- Vai alla posizione in cui hai clonato o estratto il progetto.
- Apri la cartella Ridimensionamento.
- Apri il progetto nella cartella start. Contiene il codice iniziale.
Prova l'app
- Crea ed esegui l'app
- Prova a ridimensionare l'app
Cosa ne pensi?
A seconda del supporto della compatibilità del dispositivo di test, probabilmente avrai notato che l'esperienza utente non è ideale. L'app non può essere ridimensionata e rimane bloccata nelle proporzioni iniziali. Che cosa sta succedendo?
Limitazioni del file manifest
Se esamini il file AndroidManifest.xml dell'app, puoi notare che sono state aggiunte alcune limitazioni che impediscono alla nostra app di funzionare correttamente in un ambiente di ridimensionamento delle finestre in formato libero.
AndroidManifest.xml
android:maxAspectRatio="1.4"
android:resizeableActivity="false"
android:screenOrientation="portrait">
Prova a rimuovere queste tre righe problematiche dal manifest, ricompila l'app e riprova sul dispositivo di test. Noterai che l'app non è più limitata nel ridimensionamento in formato libero. La rimozione di limitazioni come questa dal manifest è un passaggio importante per ottimizzare la tua app per il ridimensionamento delle finestre in formato libero.
3. Modifiche alla configurazione del ridimensionamento
Quando la finestra dell'app viene ridimensionata, la relativa configurazione viene aggiornata. Questi aggiornamenti hanno implicazioni per la tua app. Comprenderli e anticiparli può contribuire a offrire un'esperienza ottimale ai tuoi utenti. Le modifiche più evidenti riguardano la larghezza e l'altezza della finestra dell'app, ma queste modifiche hanno implicazioni anche per le proporzioni e l'orientamento.
Osservare le modifiche alla configurazione
Per vedere queste modifiche in un'app creata con il sistema di visualizzazione Android, puoi eseguire l'override di View.onConfigurationChanged. In Jetpack Compose, abbiamo accesso a LocalConfiguration.current, che viene aggiornato automaticamente ogni volta che viene chiamato View.onConfigurationChanged.
Per visualizzare queste modifiche alla configurazione nell'app di esempio, aggiungi un elemento componibile all'app che mostri i valori di LocalConfiguration.current oppure crea un nuovo progetto di esempio con un elemento componibile di questo tipo. Un esempio di UI per visualizzarli potrebbe essere il seguente:
val configuration = LocalConfiguration.current
val isPortrait = configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
val screenLayoutSize =
when (configuration.screenLayout and
Configuration.SCREENLAYOUT_SIZE_MASK) {
SCREENLAYOUT_SIZE_SMALL -> "SCREENLAYOUT_SIZE_SMALL"
SCREENLAYOUT_SIZE_NORMAL -> "SCREENLAYOUT_SIZE_NORMAL"
SCREENLAYOUT_SIZE_LARGE -> "SCREENLAYOUT_SIZE_LARGE"
SCREENLAYOUT_SIZE_XLARGE -> "SCREENLAYOUT_SIZE_XLARGE"
else -> "undefined value"
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("screenWidthDp: ${configuration.screenWidthDp}")
Text("screenHeightDp: ${configuration.screenHeightDp}")
Text("smallestScreenWidthDp: ${configuration.smallestScreenWidthDp}")
Text("orientation: ${if (isPortrait) "portrait" else "landscape"}")
Text("screenLayout SIZE: $screenLayoutSize")
}
Puoi vedere un'implementazione di esempio nella cartella del progetto observing-configuration-changes. Prova ad aggiungerlo alla UI della tua app, eseguilo sul dispositivo di test e osserva l'aggiornamento della UI man mano che la configurazione dell'app cambia.

Queste modifiche alla configurazione dell'app ti consentono di simulare il passaggio rapido dagli estremi che ci aspetteremmo con una schermata divisa su un piccolo smartphone allo schermo intero su un tablet o un computer. Non solo questo è un buon modo per testare il layout dell'app su più schermi, ma ti consente anche di testare la capacità dell'app di gestire eventi di modifica rapida della configurazione.
4. Logging degli eventi del ciclo di vita dell'attività
Un'altra implicazione del ridimensionamento delle finestre in formato libero per la tua app sono le varie modifiche al ciclo di vita Activity che si verificheranno per la tua app. Per visualizzare queste modifiche in tempo reale, aggiungi un osservatore del ciclo di vita al metodo onCreate e registra ogni nuovo evento del ciclo di vita eseguendo l'override di onStateChanged.
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d("resizing-codelab-lifecycle", "$event was called")
}
})
Con questo logging attivo, esegui di nuovo l'app sul dispositivo di test e guarda logcat mentre provi a ridurre a icona l'app e a portarla di nuovo in primo piano.
Osserva che l'app viene messa in pausa quando viene ridotta a icona e ripresa quando viene portata in primo piano. Ciò ha implicazioni per la tua app che esplorerai nella sezione successiva di questo codelab incentrata sulla continuità.

Ora guarda Logcat per vedere quali callback del ciclo di vita dell'attività vengono chiamati quando ridimensioni l'app dalle dimensioni più piccole possibili a quelle più grandi possibili
A seconda del dispositivo di test, potresti osservare comportamenti diversi, ma probabilmente avrai notato che l'attività viene eliminata e ricreata quando le dimensioni della finestra dell'app vengono modificate in modo significativo, ma non quando vengono modificate leggermente. Questo perché, su API 24+, solo le variazioni di dimensioni significative comportano la ricreazione di Activity.
Hai visto alcune delle modifiche alla configurazione più comuni che puoi aspettarti in un ambiente di finestre in formato libero, ma ci sono altre modifiche da tenere presenti. Ad esempio, se hai un monitor esterno collegato al dispositivo di test, puoi vedere che Activity viene eliminato e ricreato per tenere conto delle modifiche alla configurazione, come la densità di visualizzazione.
Per astrarre parte della complessità associata alle modifiche alla configurazione, utilizza API di livello superiore come WindowSizeClass per implementare la tua UI adattiva. (Vedi anche Supportare diverse dimensioni dello schermo.)
5. Continuità: mantenimento dello stato interno dei componenti combinabili quando vengono ridimensionati
Nella sezione precedente, hai visto alcune delle modifiche alla configurazione che la tua app può aspettarsi in un ambiente di ridimensionamento delle finestre in formato libero. In questa sezione, lo stato dell'interfaccia utente dell'app rimarrà continuo durante queste modifiche.
Inizia facendo in modo che la funzione componibile NavigationDrawerHeader (che si trova in ReplyHomeScreen.kt) si espanda per mostrare l'indirizzo email quando viene selezionata.
@Composable
private fun NavigationDrawerHeader(
modifier: Modifier = Modifier
) {
var showDetails by remember { mutableStateOf(false) }
Column(
modifier = modifier.clickable {
showDetails = !showDetails
}
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ReplyLogo(
modifier = Modifier
.size(dimensionResource(R.dimen.reply_logo_size))
)
ReplyProfileImage(
drawableResource = LocalAccountsDataProvider
.userAccount.avatar,
description = stringResource(id = R.string.profile),
modifier = Modifier
.size(dimensionResource(R.dimen.profile_image_size))
)
}
AnimatedVisibility (showDetails) {
Text(
text = stringResource(id = LocalAccountsDataProvider
.userAccount.email),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.padding(
start = dimensionResource(
R.dimen.drawer_padding_header),
end = dimensionResource(
R.dimen.drawer_padding_header),
bottom = dimensionResource(
R.dimen.drawer_padding_header)
),
)
}
}
}
Dopo aver aggiunto l'intestazione espandibile all'app,
- esegui l'app sul dispositivo di test
- tocca l'intestazione per espanderla
- prova a ridimensionare la finestra
Vedrai che l'intestazione perde il suo stato quando viene ridimensionata in modo significativo.

Lo stato dell'UI viene perso perché remember ti aiuta a conservare lo stato tra le ricomposizioni, ma non tra le attività o la ricreazione dei processi. È comune utilizzare il sollevamento dello stato, spostando lo stato nel chiamante di un componibile per rendere i componibili stateless, il che può evitare completamente questo problema. Detto questo, puoi utilizzare remember in alcuni casi per mantenere lo stato degli elementi dell'interfaccia utente interno alle funzioni componibili.
Per risolvere questi problemi, sostituisci remember con rememberSaveable. Questo funziona perché rememberSaveable salva e ripristina il valore memorizzato in savedInstanceState. Modifica remember in rememberSaveable, esegui l'app sul dispositivo di test e prova di nuovo a ridimensionarla. Noterai che lo stato dell'intestazione espandibile viene mantenuto durante il ridimensionamento, come previsto.
6. Evitare la duplicazione non necessaria del lavoro in background
Hai visto come puoi utilizzare rememberSaveable per conservare lo stato dell'interfaccia utente interna dei composable in seguito a modifiche alla configurazione che possono verificarsi di frequente a causa del ridimensionamento delle finestre in formato libero. Tuttavia, un'app dovrebbe spesso sollevare lo stato e la logica dell'UI dai composable. Trasferire la proprietà dello stato a un ViewModel è uno dei modi migliori per preservare lo stato durante il ridimensionamento. Quando sollevi lo stato in un ViewModel, potresti riscontrare problemi con il lavoro in background di lunga durata, come l'accesso intenso al file system o le chiamate di rete necessarie per inizializzare lo schermo.
Per visualizzare un esempio dei tipi di problemi che potresti riscontrare, aggiungi un'istruzione di log al metodo initializeUIState in ReplyViewModel.
fun initializeUIState() {
Log.d("resizing-codelab", "initializeUIState() called in the viewmodel")
val mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
Ora esegui l'app sul dispositivo di test e prova a ridimensionare la finestra dell'app più volte.
Quando esamini Logcat, noterai che l'app mostra che il metodo di inizializzazione è stato eseguito più volte. Questo può essere un problema per il lavoro che vuoi eseguire una sola volta per inizializzare la UI. Le chiamate di rete aggiuntive, l'I/O dei file o altre operazioni possono compromettere le prestazioni del dispositivo e causare altri problemi imprevisti.
Per evitare un lavoro in background non necessario, rimuovi la chiamata a initializeUIState() dal metodo onCreate() dell'attività. Inizializza invece i dati nel metodo init di ViewModel. In questo modo, il metodo di inizializzazione viene eseguito una sola volta, quando ReplyViewModel viene istanziato per la prima volta:
init {
initializeUIState()
}
Prova a eseguire di nuovo l'app e vedrai che l'attività di inizializzazione simulata non necessaria viene eseguita una sola volta, indipendentemente dal numero di volte in cui ridimensioni la finestra dell'app. Questo perché i ViewModel persistono oltre il ciclo di vita di Activity. Eseguendo il codice di inizializzazione una sola volta al momento della creazione di ViewModel, lo separiamo da qualsiasi ricreazione di Activity ed evitiamo lavoro non necessario. Se si trattasse di una chiamata al server costosa o di un'operazione di I/O di file pesante per inizializzare la UI, risparmieresti risorse significative e miglioreresti l'esperienza utente.
7. CONGRATULAZIONI!
Ce l'hai fatta! Ottimo! Ora hai implementato alcune best practice per consentire alle app per Android di ridimensionarsi correttamente su ChromeOS e in altri ambienti multi-finestra e multi-schermo.
Codice sorgente di esempio
Clona il repository da GitHub
git clone https://github.com/android/large-screen-codelabs/
… oppure scarica un file ZIP del repository ed estrailo.