Inserti di finestre in Scrivi

La piattaforma Android è responsabile del disegno dell'interfaccia utente di sistema, ad esempio la barra di stato e la barra di navigazione. Questa UI di sistema viene visualizzata indipendentemente dall'app usata dall'utente. WindowInsets fornisce informazioni sull'UI di sistema per fare in modo che l'app venga visualizzata nell'area corretta e che non sia oscurata dalla UI del sistema.

Andando da un bordo all'altro per disegnare dietro le barre di sistema
Figura 1. Andare da un bordo all'altro per disegnare dietro le barre di sistema

Per impostazione predefinita, la UI dell'app è limitata alla struttura all'interno dell'UI di sistema, ad esempio la barra di stato e la barra di navigazione. Ciò garantisce che i contenuti dell'app non siano oscurati da elementi dell'interfaccia utente di sistema.

Tuttavia, consigliamo di attivare la visualizzazione delle app in queste aree in cui viene visualizzata anche l'UI di sistema, in modo da offrire un'esperienza utente più fluida e consentire all'app di sfruttare appieno lo spazio occupato dalle finestre a sua disposizione. Ciò consente anche di animare le app insieme all'interfaccia utente di sistema, soprattutto quando viene mostrata e nascosta la tastiera software.

L'attivazione della visualizzazione in queste aree geografiche e la visualizzazione di contenuti dietro l'interfaccia utente del sistema è chiamata on-edge-to-edge. In questa pagina scoprirai i diversi tipi di inserti, come scegliere di passare da un bordo all'altro e come utilizzare le API integrate per animare l'interfaccia utente ed evitare di oscurare parti dell'app.

Nozioni di base sugli strumenti

Quando un'app passa da un bordo all'altro, devi assicurarti che le interazioni e i contenuti importanti non siano oscurati dall'interfaccia utente di sistema. Ad esempio, se un pulsante viene posizionato dietro la barra di navigazione, l'utente potrebbe non essere in grado di selezionarlo.

Le dimensioni dell'interfaccia utente di sistema e le informazioni su dove è posizionata vengono specificate tramite inset.

A ogni porzione dell'interfaccia utente di sistema corrisponde un tipo di riquadro che descrive le dimensioni e la posizione. Ad esempio, gli insiemi della barra di stato forniscono le dimensioni e la posizione della barra di stato, mentre quelli della barra di navigazione forniscono le dimensioni e la posizione della barra. Ogni tipo di riquadro è costituito da quattro dimensioni in pixel: in alto, a sinistra, a destra e in basso. Queste dimensioni specificano la distanza dell'interfaccia utente del sistema dai lati corrispondenti della finestra dell'app. Pertanto, per evitare sovrapposizioni con quel tipo di UI di sistema, l'UI dell'app deve quindi essere impostata in base a quella quantità.

Questi tipi di inserti Android integrati sono disponibili tramite WindowInsets:

WindowInsets.statusBars

Elementi che descrivono le barre di stato. Si tratta delle barre superiori dell'interfaccia utente del sistema che contengono icone di notifica e altri indicatori.

WindowInsets.statusBarsIgnoringVisibility

Nella barra di stato viene inserito il momento in cui sono visibili. Se le barre di stato sono attualmente nascoste (a causa dell'attivazione della modalità a schermo intero immersiva), i riquadri della barra di stato principale saranno vuoti, ma non saranno vuoti.

WindowInsets.navigationBars

Elementi che descrivono le barre di navigazione. Si tratta delle barre dell'interfaccia utente di sistema sul lato sinistro, destro o inferiore del dispositivo che descrivono la barra delle applicazioni o le icone di navigazione. Questi parametri possono cambiare in fase di runtime in base al metodo di navigazione preferito dell'utente e all'interazione con la barra delle app.

WindowInsets.navigationBarsIgnoringVisibility

Gli elementi della barra di navigazione sono visibili quando sono visibili. Se al momento le barre di navigazione sono nascoste (a causa dell'attivazione della modalità a schermo intero immersiva), i riquadri della barra di navigazione principale saranno vuoti, ma non saranno vuoti.

WindowInsets.captionBar

Riquadro che descrive la decorazione della finestra dell'interfaccia utente di sistema se si trova in una finestra in formato libero, ad esempio la barra del titolo superiore.

WindowInsets.captionBarIgnoringVisibility

La barra dei sottotitoli codificati è integrata per indicare quando sono visibili. Se al momento le barre dei sottotitoli codificati sono nascoste, gli insiemi della barra dei sottotitoli principali saranno vuoti, ma non saranno vuoti.

WindowInsets.systemBars

L'unione degli insiemi della barra di sistema, che includono le barre di stato, le barre di navigazione e la barra dei sottotitoli codificati.

WindowInsets.systemBarsIgnoringVisibility

La barra di sistema viene inserita per indicare quando sono visibili. Se al momento le barre di sistema sono nascoste (a causa dell'attivazione della modalità a schermo intero immersiva), i riquadri della barra di sistema principale saranno vuoti, ma non saranno vuoti.

WindowInsets.ime

I riquadri che descrivono la quantità di spazio nella parte inferiore occupata dalla tastiera software.

WindowInsets.imeAnimationSource

I riquadri che descrivono la quantità di spazio occupata dalla tastiera software prima dell'animazione della tastiera corrente.

WindowInsets.imeAnimationTarget

I riquadri che descrivono la quantità di spazio che la tastiera software occupa dopo l'animazione della tastiera corrente.

WindowInsets.tappableElement

Un tipo di riquadri che descrivono informazioni più dettagliate sull'UI di navigazione, che fornisce la quantità di spazio in cui i "tocchi" verranno gestiti dal sistema e non dall'app. Per barre di navigazione trasparenti con navigazione tramite gesti, è possibile toccare alcuni elementi dell'app tramite l'UI di navigazione del sistema.

WindowInsets.tappableElementIgnoringVisibility

L'elemento toccabile viene inserito per il momento in cui sono visibili. Se gli elementi toccabili sono attualmente nascosti (a causa dell'attivazione della modalità a schermo intero immersivo), i riquadri degli elementi toccabili principali saranno vuoti, ma non saranno vuoti.

WindowInsets.systemGestures

I riquadri che rappresentano la quantità di riquadri in cui il sistema intercetta i gesti per la navigazione. Le app possono specificare manualmente la gestione di un numero limitato di questi gesti tramite Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Un sottoinsieme dei gesti di sistema che verranno sempre gestiti dal sistema e che non è possibile disattivare tramite Modifier.systemGestureExclusion.

WindowInsets.displayCutout

I riquadri che rappresentano la quantità di spazio necessaria per evitare sovrapposizioni con un taglio sul display (tacca o foro).

WindowInsets.waterfall

Inserti che rappresentano le aree curve di un display a cascata. Un display a cascata presenta aree curve lungo i bordi dello schermo dove lo schermo inizia ad avvolgersi lungo i lati del dispositivo.

Questi tipi sono riassunti da tre tipi di inserti "sicuri" che assicurano che i contenuti non siano oscurati:

Questi tipi di inserti "sicuri" proteggono i contenuti in diversi modi, a seconda degli insiemi sottostanti della piattaforma:

  • Utilizza WindowInsets.safeDrawing per proteggere i contenuti che non devono essere tracciati sotto alcuna UI di sistema. Questo è l'uso più comune degli insiemi: per impedire la visualizzazione di contenuti oscurati dall'interfaccia utente di sistema (parzialmente o completamente).
  • Usa WindowInsets.safeGestures per proteggere i contenuti con i gesti. In questo modo i gesti di sistema non si scontrano con quelli delle app (ad esempio quelli per schede inferiori, caroselli o giochi).
  • Usa WindowInsets.safeContent con una combinazione di WindowInsets.safeDrawing e WindowInsets.safeGestures per assicurarti che i contenuti non abbiano sovrapposizioni visive e gestuali.

Configurazione degli inserti

Per consentire alla tua app di avere il controllo completo sui punti in cui vengono disegnati contenuti, segui questi passaggi di configurazione. Senza questi passaggi, l'app potrebbe disegnare colori neri o a tinta unita dietro l'interfaccia utente di sistema o non animarsi in modo sincrono con la tastiera software.

  1. Chiama enableEdgeToEdge() a Activity.onCreate. Questa chiamata richiede che la tua app venga visualizzata dietro l'interfaccia utente di sistema. L'app potrà quindi controllare il modo in cui questi riquadri vengono utilizzati per modificare l'UI.
  2. Imposta android:windowSoftInputMode="adjustResize" nella voce AndroidManifest.xml della tua Attività. Questa impostazione consente all'app di ricevere le dimensioni dell'IME del software sotto forma di riquadri, che puoi usare per riempire e organizzare i contenuti in modo appropriato quando l'IME viene visualizzato e scompare nell'app.

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

API Compose

Quando l'attività ha assunto il controllo della gestione di tutti gli insiemi, puoi utilizzare le API Compose per assicurarti che i contenuti non siano oscurati e che gli elementi con cui è possibile interagire non si sovrappongano con l'UI di sistema. Queste API sincronizzano anche il layout dell'app con le modifiche inserite.

Ad esempio, questo è il metodo più di base per applicare gli insiemi ai contenuti dell'intera app:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

Questo snippet applica i riquadri della finestra safeDrawing come spaziatura interna attorno all'intero contenuto dell'app. Anche se ciò garantisce che gli elementi con cui è possibile interagire non si sovrappongano con l'interfaccia utente di sistema, significa anche che nessuna delle app verrà spostata dietro l'interfaccia utente del sistema per ottenere un effetto a livello perimetrale. Per utilizzare al meglio l'intera finestra, devi ottimizzare il punto in cui vengono applicati gli insiemi a livello di schermata o di componente per componente.

Tutti questi tipi di inserti sono animati automaticamente con animazioni IME riportate nell'API 21. Di conseguenza, anche tutti i layout che utilizzano questi riquadri vengono animati automaticamente quando i valori inseriti cambiano.

Esistono due modi principali per utilizzare questi tipi di riquadri per regolare i layout componibili: i modificatori di spaziatura interna e i modificatori di dimensione inseriti.

Modificatori di spaziatura

Modifier.windowInsetsPadding(windowInsets: WindowInsets) applica i riquadri delle finestre indicati come spaziatura interna, agendo come farebbe Modifier.padding. Ad esempio, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) applica i riquadri del disegno sicuro come spaziatura interna su tutti e quattro i lati.

Esistono anche diversi metodi di utilità integrate per i tipi di inserti più comuni. Modifier.safeDrawingPadding() è uno di questi metodi, equivalente a Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Esistono modificatori analoghi per gli altri tipi di inserti.

Modificatori di dimensioni inseriti

I seguenti modificatori applicano una quantità di riquadri di finestre impostando la dimensione del componente in modo che corrisponda a quella degli insiemi:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Applica il lato iniziale di windowInsets alla larghezza (ad esempio Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Applica il lato finale di windowInsets come larghezza (ad esempio Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Applica il lato superiore degli elementi windowInsets come altezza (ad esempio Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Applica il lato inferiore degli elementi windowInsets come altezza (ad esempio Modifier.height)

Questi modificatori sono particolarmente utili per dimensionare un Spacer che occupa lo spazio degli insiemi:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Consumo integrato

I modificatori di spaziatura interna inseriti (windowInsetsPadding e aiutanti come safeDrawingPadding) utilizzano automaticamente la parte degli insiemi che viene applicata come spaziatura interna. Mentre approfondisci l'albero della composizione, i modificatori di spaziatura interna nidificati e i modificatori di dimensione interna sanno che una parte degli insiemi è già stata utilizzata dai modificatori di spaziatura interna esterni ed evita di usare la stessa parte degli insiemi più di una volta, il che comporterebbe troppo spazio in più.

Inoltre, i modificatori di dimensione degli inserti evitano di utilizzare la stessa parte degli insiemi più di una volta se questi sono già stati utilizzati. Tuttavia, poiché cambiano dimensione direttamente, non consumano gli inserti.

Di conseguenza, i modificatori di spaziatura interna nidificati modificano automaticamente la quantità di spaziatura interna applicata a ogni componibile.

Guardando allo stesso esempio di LazyColumn di prima, LazyColumn viene ridimensionato dal modificatore imePadding. All'interno di LazyColumn, l'ultimo elemento ha le dimensioni corrispondenti alla parte inferiore delle barre di sistema:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Quando l'IME è chiuso, il modificatore imePadding() non applica spaziatura interna perché l'IME non ha un'altezza. Poiché il modificatore imePadding() non applica alcuna spaziatura interna, non vengono utilizzati riquadri e l'altezza del Spacer corrisponderà alla dimensione del lato inferiore delle barre di sistema.

Quando l'IME si apre, i riquadri IME si animano per corrispondere alle dimensioni dell'IME e il modificatore imePadding() inizia ad applicare una spaziatura interna inferiore per ridimensionare l'LazyColumn all'apertura dell'IME. Quando il modificatore imePadding() inizia ad applicare la spaziatura interna inferiore, inizia a consumare quella quantità di inserti. Pertanto, l'altezza di Spacer inizia a diminuire, poiché la spaziatura per le barre di sistema è già stata applicata dal modificatore imePadding(). Dopo che il modificatore imePadding() ha applicato una quantità di spaziatura interna inferiore superiore alle barre di sistema, l'altezza del campo Spacer è pari a zero.

Quando l'IME si chiude, le modifiche vengono apportate in senso inverso: l'Spacer si espande da un'altezza pari a zero quando l'IME imePadding() viene applicato meno del lato inferiore delle barre di sistema, finché l'IME Spacer corrisponde all'altezza del lato inferiore delle barre di sistema una volta completata l'animazione dell'IME.

Figura 2. Colonna lazy-to-edge con TextField

Questo comportamento si ottiene attraverso la comunicazione tra tutti i modificatori windowInsetsPadding e può essere influenzato in un paio di altri modi.

Anche Modifier.consumeWindowInsets(insets: WindowInsets) utilizza gli insiemi allo stesso modo di Modifier.windowInsetsPadding, ma non applica gli inserti consumati come spaziatura interna. Questo è utile in combinazione con i modificatori delle dimensioni inset, per indicare ai fratelli che una determinata quantità di inserti è già stata utilizzata:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) si comporta in modo molto simile alla versione con un argomento WindowInsets, ma richiede un utilizzo arbitrario di PaddingValues. Questo è utile per informare i bambini quando la spaziatura interna o la spaziatura sono fornite da un meccanismo diverso dai modificatori di spaziatura interna, come un normale Modifier.padding o distanziali di altezza fissa:

@OptIn(ExperimentalLayoutApi::class)
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Nei casi in cui sono necessari insiemi delle finestre non elaborate senza consumo, utilizza direttamente i valori WindowInsets o utilizza WindowInsets.asPaddingValues() per restituire un PaddingValues degli insiemi non interessati dal consumo. Tuttavia, per le avvertenze riportate di seguito, preferisci utilizzare, ove possibile, i modificatori di dimensione dei riquadri delle finestre e i modificatori di dimensione dei riquadri delle finestre.

Fasi di Inset e Jetpack Compose

Compose utilizza le API AndroidX principali per aggiornare e animare gli insiemi, che utilizzano le API della piattaforma sottostanti per la gestione degli insiemi. A causa di questo comportamento della piattaforma, gli insiemi hanno un rapporto speciale con le fasi di Jetpack Compose.

Il valore degli insiemi viene aggiornato dopo la fase di composizione, ma prima della fase di layout. Ciò significa che la lettura del valore degli insiemi nella composizione di solito utilizza un valore degli insiemi di un frame in ritardo. I modificatori integrati descritti in questa pagina sono creati per ritardare l'utilizzo dei valori degli inset fino alla fase di layout, il che garantisce che i valori inseriti vengano utilizzati sullo stesso frame durante l'aggiornamento.

Animazioni IME della tastiera con WindowInsets

Puoi applicare Modifier.imeNestedScroll() a un contenitore a scorrimento per aprire e chiudere automaticamente l'IME quando scorri fino alla parte inferiore del contenitore.

class WindowInsetsExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

Animazione che mostra un elemento UI che scorre verso l&#39;alto e verso il basso per far posto a una tastiera

Figura 1. Animazioni IME

Supporto integrato per i componenti di Material 3

Per motivi di facilità d'uso, molti dei componenti componibili Material 3 integrati (androidx.compose.material3) gestiscono autonomamente gli inserti in base al modo in cui vengono posizionati nell'app in base alle specifiche relative a Material.

Componenti componibili con gestione integrata

Di seguito è riportato un elenco dei componenti Material che gestiscono automaticamente gli insiemi.

Barre delle app

Contenitori di contenuti

Impalcatura

Per impostazione predefinita, Scaffold fornisce degli insiemi come parametro paddingValues che puoi consumare e utilizzare. Scaffold non applica gli insiemi ai contenuti; questa responsabilità è tua. Ad esempio, per consumare questi riquadri con un LazyColumn all'interno di un Scaffold:

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

Esegui override degli insiemi predefiniti

Puoi modificare il parametro windowInsets passato al componibile per configurare il comportamento del componibile. Questo parametro può essere un tipo diverso di riquadro di finestre da applicare oppure può essere disattivato passando un'istanza vuota: WindowInsets(0, 0, 0, 0).

Ad esempio, per disabilitare la gestione integrata su LargeTopAppBar, imposta il parametro windowInsets su un'istanza vuota:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

Interoperabilità con i riquadri del sistema View

Potrebbe essere necessario eseguire l'override dei riquadri predefiniti quando lo schermo ha sia il codice Visualizzazioni sia Scrivi nella stessa gerarchia. In questo caso, devi indicare esplicitamente in quale quello utilizzare gli inset e in quale ignorarli.

Ad esempio, se il layout più esterno è un layout Android View, devi utilizzare gli insiemi nel sistema di visualizzazione e ignorarli per Compose. In alternativa, se il layout più esterno è un componibile, dovresti utilizzare gli inserti di Compose e incollare i componibili AndroidView di conseguenza.

Per impostazione predefinita, ogni elemento ComposeView consuma tutti gli insiemi al livello di consumo WindowInsetsCompat. Per modificare questo comportamento predefinito, imposta ComposeView.consumeWindowInsets su false.

Risorse

  • Ora in Android: un'app per Android completamente funzionale realizzata interamente con Kotlin e Jetpack Compose.