Contenuti

Gestire gli Insets della Status Bar in Compose

Contenuti

Lavorando ad un progetto personale sono incappato in un problema dovuto ai nuovi comportamenti di Android; in particolare l’applicazione forzata dell’edge to edge a partire da android 15 (sdk 35).

La documentazione è chiara a riguardo e l’intento è quello di forzare ogni app a svecchiarsi e disegnare il proprio contentuto sotto le barre di sistema (o quantomeno gestirle).

Vediamo più da vicino la problematica:

/images/insets/insets1.png

Come si può vedere dall’immagine l’inset della status bar (parte alta in giallo) sembra essere duplicato. La navigation bar invece è corretta. L’immagine fa riferimento a questo snippet di codice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

Scaffold(
    modifier = modifier,
) { paddingValues ->

    Box(
        modifier = Modifier.fillMaxSize()
            .background(Color.Yellow)
            .padding(paddingValues)
            .consumeWindowInsets(paddingValues)
    ) {

        Box(modifier = Modifier.fillMaxSize().background(Color.Red))
    }
}

Lo Scaffold di default dovrebbe gestire gli insets per entrambe le barre (status e navigation) tramite l’argomento ScaffoldDefaults.contentWindowInsets. Questo significa ricevere nella lambda il parametro paddingValues da applicare al contenuto principale che nel mio caso è il composable Box esterno. Siccome stiamo applicando il padding manualmente dobbiamo poi notificare al sistema che abbiamo gestito o consumato una porzione di questi inset.

Dov’è l’errore? Teoricamente la parte gialla in alto non dovrebbe vedersi ma disegnarsi sotto la barra delle notifiche.

Il punto è che non sempre si desidera disegnare sotto queste barre. Per alcune applicazioni può aver senso continuare ad avere il comportamento di sempre con la status bar di un colore solido e non trasparente o sfumato. Per ottenere questo comportamento bisogna prestare attenzione alla versione di android utilizzata dall’utente e nel corso degli ultimi anni alcune soluzioni sono state deprecate.

Ad oggi il metodo corretto sembra essere questo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private fun setStatusBarColor(window: Window, color: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
        window.decorView.setOnApplyWindowInsetsListener { view, insets ->
            val statusBarInsets = insets.getInsets(WindowInsets.Type.statusBars())
            view.setBackgroundColor(color)
            view.setPadding(0, statusBarInsets.top, 0, 0)
            insets
        }
    } else {
        window.statusBarColor = color
    }
}

Per i device pre Android 15 si può usare la proprietà statusBarColor mentre per quelli succesivi bisogna gestire gli inset e colorare la status bar manualmente. La prima cosa che salta all’occhio è che in questo caso stiamo lavorando con le view e non tramite composables. Fortunatamente tra i due mondi c’è un ponte; se un inset viene consumato tramite view questo non verrà duplicato in compose.

Il problema del precedente snippet è proprio questo. Non sono stati gestiti correttamente gli spazi della status bar e compose tramite Scaffold lo ha riapplicato. Il valore di ritorno del metodo setOnApplyWindowInsetsListener deve rimuovere dagli inset originali la quantità consumata.

Aggiustando il tiro quindi:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private fun setStatusBarColor(window: Window, color: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
        window.decorView.setOnApplyWindowInsetsListener { view, insets ->
            val statusBarInsets = insets.getInsets(WindowInsets.Type.statusBars())
            view.setBackgroundColor(color)
            view.setPadding(0, statusBarInsets.top, 0, 0)
            // consume status bar insets (applied manually above)
            WindowInsets.Builder(insets)
                .setInsets(WindowInsets.Type.statusBars(), android.graphics.Insets.NONE)
                .build()
        }
    } else {
        window.statusBarColor = color
    }
}

Ora gli insets vengono calcolati e gestiti correttamente

/images/insets/insets2.png