1. Introduzione
Per Android 10 o versioni successive, i gesti di navigazione sono supportati come nuova modalità. In questo modo, la tua app può utilizzare l'intero schermo e offrire un'esperienza di visualizzazione più immersiva. Quando l'utente scorre verso l'alto dal bordo inferiore dello schermo, viene visualizzata la schermata Home di Android. Quando scorrono verso l'interno dai bordi sinistro o destro, tornano alla schermata precedente.
Con questi due gesti, la tua app può sfruttare lo spazio nella parte inferiore dello schermo. Tuttavia, se la tua app utilizza i gesti o ha controlli nelle aree dei gesti di sistema, potrebbe creare conflitti con i gesti a livello di sistema.
Questo codelab ha lo scopo di insegnarti a utilizzare gli inset per evitare conflitti di gesti. Inoltre, questo codelab ha lo scopo di insegnarti a utilizzare l'API Gesture Exclusion per i controlli, ad esempio le maniglie di trascinamento, che devono trovarsi nelle zone dei gesti.
Cosa imparerai
- Come utilizzare i listener di inserimento nelle visualizzazioni
- Come utilizzare l'API Gesture Exclusion
- Come si comporta la modalità immersiva quando i gesti sono attivi
Questo codelab ha lo scopo di rendere la tua app compatibile con le gesture di sistema. Concetti e blocchi di codice non pertinenti sono trattati solo superficialmente e sono forniti solo per operazioni di copia e incolla.
Cosa creerai
Universal Android Music Player (UAMP) è un'app di esempio per la riproduzione di musica per Android scritta in Kotlin. Configurerai UAMP per la navigazione tramite gesti.
- Utilizzare gli inset per allontanare i controlli dalle aree dei gesti
- Utilizza l'API Gesture Exclusion per disattivare il gesto Indietro per i controlli in conflitto
- Utilizzare le build per esplorare le modifiche al comportamento della modalità immersiva con la navigazione tramite gesti
Che cosa ti serve
- Un dispositivo o un emulatore con Android 10 o versioni successive
- Android Studio
2. Panoramica app
Universal Android Music Player (UAMP) è un'app di esempio per la riproduzione di musica per Android scritta in Kotlin. Supporta funzionalità che includono la riproduzione in background, la gestione della messa a fuoco audio, l'integrazione dell'assistente e più piattaforme come Wear, TV e Auto.
|
|
|
Figura 1: un flusso in UAMP
UAMP carica un catalogo musicale da un server remoto e consente all'utente di sfogliare gli album e i brani. L'utente tocca un brano e lo riproduce tramite cuffie o altoparlanti connessi. L'app non è progettata per funzionare con i gesti di sistema. Pertanto, quando esegui UAMP su un dispositivo con Android 10 o versioni successive, inizialmente riscontri alcuni problemi.
3. Configurazione
Per ottenere l'app di esempio, clona il repository da GitHub e passa al branch starter:
$ git clone https://github.com/googlecodelabs/android-gestural-navigation/
In alternativa, puoi scaricare il repository come file ZIP, decomprimerlo e aprirlo in Android Studio.
Completa i seguenti passaggi:
- Apri e crea l'app in Android Studio.
- Crea un nuovo dispositivo virtuale e seleziona livello API 29. In alternativa, puoi collegare un dispositivo reale con livello API 29 o superiore.
- Esegui l'app. L'elenco che vedi raggruppa i brani nelle sezioni Consigliati e Album.
- Fai clic su Consigliati e seleziona un brano dall'elenco.
- L'app inizia la riproduzione del brano.
Attivare la navigazione tramite gesti
Se esegui una nuova istanza dell'emulatore con il livello API 29, la navigazione tramite gesti potrebbe non essere attivata per impostazione predefinita. Per attivare la navigazione tramite gesti, seleziona Impostazioni di sistema > Sistema > Navigazione del sistema > Navigazione tramite gesti.
Eseguire l'app con la navigazione tramite gesti
Se esegui l'app con la navigazione tramite gesti attivata e avvii la riproduzione di un brano, potresti notare che i controlli del player sono molto vicini alle aree dei gesti Home e Indietro.
4. Passare alla visualizzazione edge-to-edge
Che cos'è edge-to-edge?
Le app eseguite su Android 10 o versioni successive possono offrire un'esperienza a schermo intero, indipendentemente dal fatto che i gesti o i pulsanti siano abilitati per la navigazione. Per offrire un'esperienza edge-to-edge, le app devono essere disegnate dietro le barre di navigazione e di stato trasparenti.
Disegna dietro la barra di navigazione
Affinché la tua app esegua il rendering dei contenuti sotto la barra di navigazione, devi prima rendere trasparente lo sfondo della barra di navigazione. Dopodiché, devi rendere trasparente la barra di stato. In questo modo, l'app può essere visualizzata per l'intera altezza dello schermo.
Per modificare il colore della barra di navigazione e della barra di stato:
- Barra di navigazione:apri
res/values-29/styles.xmle impostanavigationBarColorsucolor/transparent. - Barra di stato:imposta
statusBarColorsucolor/transparent.
Esamina il seguente esempio di codice di res/values-29/styles.xml:
<!-- change navigation bar color -->
<item name="android:navigationBarColor">
@android:color/transparent
</item>
<!-- change status bar color -->
<item name="android:statusBarColor">
@android:color/transparent
</item>
Flag di visibilità della UI di sistema
Devi anche impostare i flag di visibilità dell'interfaccia utente di sistema per indicare al sistema di disporre l'app sotto le barre di sistema. Le API systemUiVisibility nella classe View consentono di impostare una serie di flag. Segui questi passaggi:
- Apri la classe
MainActivity.kte trova il metodoonCreate(). Ottieni un'istanza difragmentContainer. - Imposta i seguenti valori su
content.systemUiVisibility:
View.SYSTEM_UI_FLAG_LAYOUT_STABLEView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREENView.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Esamina il seguente esempio di codice di MainActivity.kt:
val content: FrameLayout = findViewById(R.id.fragmentContainer)
content.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Quando imposti questi flag insieme, indichi al sistema che vuoi che la tua app venga visualizzata a schermo intero come se le barre di navigazione e di stato non fossero presenti. Segui questi passaggi:
- Esegui l'app e, per passare alla schermata del player, seleziona un brano da riprodurre.
- Verifica che i controlli del player siano disegnati sotto la barra di navigazione, rendendoli difficili da raggiungere:
|
|
- Vai alle impostazioni di sistema, torna alla modalità di navigazione con tre pulsanti e torna all'app.
- Verifica che i controlli siano ancora più difficili da usare con la barra di navigazione a tre pulsanti: nota che il pulsante
SeekBarè nascosto dietro la barra di navigazione e che il pulsante Riproduci/Pausa è coperto quasi interamente dalla barra di navigazione. - Esplora e sperimenta un po'. Al termine, vai alle impostazioni di sistema e torna alla navigazione tramite gesti:

L'app ora viene visualizzata da bordo a bordo, ma ci sono problemi di usabilità, controlli dell'app in conflitto e sovrapposti, che devono essere risolti.
5. Inset
WindowInsets indica all'app dove viene visualizzata l'interfaccia utente di sistema sopra i contenuti, nonché le regioni dello schermo in cui i gesti di sistema hanno la priorità sui gesti in-app. Gli inserti sono rappresentati dalla classe WindowInsets e dalla classe WindowInsetsCompat in Jetpack. Ti consigliamo vivamente di utilizzare WindowInsetsCompat per garantire un comportamento coerente in tutti i livelli API.
Inserti di sistema e inserti di sistema obbligatori
Le seguenti API di inset sono i tipi di inset più comunemente utilizzati:
- Inset della finestra di sistema:indicano dove viene visualizzata la UI di sistema sopra l'app. Spieghiamo come utilizzare gli inset di sistema per allontanare i controlli dalle barre di sistema.
- Inset dei gesti di sistema:restituiscono tutte le aree dei gesti. I controlli di scorrimento in-app in queste regioni possono attivare accidentalmente i gesti di sistema.
- Margini interni dei gesti obbligatori:sono un sottoinsieme dei margini interni dei gesti di sistema e non possono essere sostituiti. Indicano le aree dello schermo in cui il comportamento dei gesti di sistema avrà sempre la priorità sui gesti in-app.
Utilizzare gli inserti per spostare i controlli delle app
Ora che hai maggiori informazioni sulle API inset, puoi correggere i controlli delle app, come descritto nei passaggi seguenti:
- Ottieni un'istanza di
playerLayoutdall'istanza dell'oggettoview. - Aggiungi un
OnApplyWindowInsetsListeneraplayerView. - Allontana la visualizzazione dall'area dei gesti: trova il valore di inserimento del sistema per la parte inferiore e aumenta il padding della visualizzazione di questo valore. Per aggiornare il padding della visualizzazione di conseguenza, aggiungi [il valore associato al padding inferiore dell'app] a [il valore associato al valore di rientro inferiore del sistema].
Esamina il seguente esempio di codice di NowPlayingFragment.kt:
playerView = view.findViewById(R.id.playerLayout)
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(
bottom = insets.systemWindowInsetBottom + view.paddingBottom
)
insets
}
- Esegui l'app e seleziona un brano. Nota che non sembra cambiare nulla nei controlli del player. Se aggiungi un punto di interruzione ed esegui il debug dell'app, vedrai che il listener non viene chiamato.
- Per risolvere il problema, passa a
FragmentContainerView, che lo gestisce automaticamente. Apriactivity_main.xmle modificaFrameLayoutinFragmentContainerView.
Esamina il seguente esempio di codice di activity_main.xml:
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragmentContainer"
tools:context="com.example.android.uamp.MainActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- Esegui di nuovo l'app e vai alla schermata del player. I controlli del player in basso sono spostati lontano dall'area dei gesti in basso.
I controlli dell'app ora funzionano con la navigazione tramite gesti, ma si spostano più del previsto. Devi risolvere il problema.
Mantenere l'imbottitura e i margini attuali
Se passi ad altre app o vai alla schermata Home e torni all'app senza chiuderla, noterai che i controlli del player si spostano verso l'alto ogni volta.
Questo perché l'app attiva requestApplyInsets() ogni volta che l'attività inizia. Anche senza questa chiamata, WindowInsets può essere inviato più volte in qualsiasi momento durante il ciclo di vita di una visualizzazione.
L'attuale InsetListener su playerView funziona perfettamente la prima volta quando aggiungi l'importo del valore del rientro inferiore al valore del padding inferiore dell'app dichiarato in activity_main.xml. Le chiamate successive, tuttavia, continuano ad aggiungere il valore del margine interno inferiore al padding inferiore della visualizzazione già aggiornata.
Per risolvere il problema, segui questi passaggi:
- Registra il valore iniziale del padding della visualizzazione. Crea un nuovo valore e memorizza il valore iniziale del padding della visualizzazione di
playerView, appena prima del codice del listener.
Esamina il seguente esempio di codice di NowPlayingFragment.kt:
val initialPadding = playerView.paddingBottom
- Utilizza questo valore iniziale per aggiornare il padding inferiore della visualizzazione, in modo da evitare l'utilizzo del valore corrente del padding inferiore dell'app.
Esamina il seguente esempio di codice di NowPlayingFragment.kt:
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(bottom = insets.systemWindowInsetBottom + initialPadding)
insets
}
- Esegui di nuovo l'app. Passare da un'app all'altra e andare alla schermata Home. Quando torni all'app, i controlli del player si trovano appena sopra l'area dei gesti.
Riprogettazione dei controlli delle app
La barra di ricerca del player è troppo vicina all'area dei gesti in basso, il che significa che l'utente può attivare accidentalmente il gesto della home page quando completa uno scorrimento orizzontale. Se aumenti ulteriormente il padding, il problema potrebbe risolversi, ma il player potrebbe anche spostarsi più in alto di quanto desiderato.
L'utilizzo degli inset ti consente di risolvere i conflitti di gesti, ma a volte, con piccole modifiche al design, puoi evitarli del tutto. Per riprogettare i controlli del player ed evitare conflitti con i gesti, segui questi passaggi:
- Apri
fragment_nowplaying.xml. Passa alla visualizzazione Struttura e selezionaSeekBarin basso:

- Passa alla Vista codice.
- Per spostare
SeekBarnella parte superiore diplayerLayout, modificalayout_constraintTop_toBottomOfdella barra di ricerca inparent. - Per vincolare altri elementi in
playerViewalla parte inferiore diSeekBar, modificalayout_constraintTop_toTopOfda elemento principale a@+id/seekBarinmedia_button,titleeposition.
Esamina il seguente esempio di codice di fragment_nowplaying.xml:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_gravity="bottom"
android:background="@drawable/media_overlay_background"
android:id="@+id/playerLayout">
<ImageButton
android:id="@+id/media_button"
android:layout_width="@dimen/exo_media_button_width"
android:layout_height="@dimen/exo_media_button_height"
android:background="?attr/selectableItemBackground"
android:scaleType="centerInside"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/seekBar"
app:srcCompat="@drawable/ic_play_arrow_black_24dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Title"
app:layout_constraintTop_toTopOf="@+id/seekBar"
app:layout_constraintLeft_toRightOf="@id/media_button"
app:layout_constraintRight_toLeftOf="@id/position"
tools:text="Song Title" />
<TextView
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Subtitle"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintLeft_toRightOf="@id/media_button"
app:layout_constraintRight_toLeftOf="@id/position"
tools:text="Artist" />
<TextView
android:id="@+id/position"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Title"
app:layout_constraintTop_toTopOf="@+id/seekBar"
app:layout_constraintRight_toRightOf="parent"
tools:text="0:00" />
<TextView
android:id="@+id/duration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Subtitle"
app:layout_constraintTop_toBottomOf="@id/position"
app:layout_constraintRight_toRightOf="parent"
tools:text="0:00" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Esegui l'app e interagisci con il player e la barra di ricerca.
Queste modifiche minime al design migliorano notevolmente l'app.
6. API Gesture Exclusion
I controlli di riproduzione per i conflitti di gesture nell'area delle gesture della casa sono stati corretti. L'area del gesto Indietro può anche creare conflitti con i controlli dell'app. Il seguente screenshot mostra che la barra di ricerca del player si trova attualmente nelle aree dei gesti indietro a destra e a sinistra:

SeekBar gestisce automaticamente i conflitti di gesture. Tuttavia, potresti dover utilizzare altri componenti UI che attivano conflitti di gesti. In questi casi, puoi utilizzare Gesture Exclusion API per disattivare parzialmente il gesto Indietro.
Utilizzare l'API Gesture Exclusion
Per creare una zona di esclusione dei gesti, chiama setSystemGestureExclusionRects() nella visualizzazione con un elenco di oggetti rect. Questi oggetti rect corrispondono alle coordinate delle aree rettangolari escluse. Questa chiamata deve essere eseguita nei metodi onLayout() o onDraw() della visualizzazione. Per farlo, segui questi passaggi:
- Crea un nuovo pacchetto denominato
view. - Per chiamare questa API, crea una nuova classe denominata
MySeekBared estendiAppCompatSeekBar.
Esamina il seguente esempio di codice di MySeekBar.kt:
class MySeekBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = android.R.attr.seekBarStyle
) : androidx.appcompat.widget.AppCompatSeekBar(context, attrs, defStyle) {
}
- Crea un nuovo metodo denominato
updateGestureExclusion().
Esamina il seguente esempio di codice di MySeekBar.kt:
private fun updateGestureExclusion() {
}
- Aggiungi un controllo per ignorare questa chiamata al livello API 28 o inferiore.
Esamina il seguente esempio di codice di MySeekBar.kt:
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
}
- Poiché l'API Gesture Exclusion ha un limite di 200 dp, escludi solo il pollice della barra di ricerca. Ottieni una copia dei limiti della barra di ricerca e aggiungi ogni oggetto a un elenco modificabile.
Esamina il seguente esempio di codice di MySeekBar.kt:
private val gestureExclusionRects = mutableListOf<Rect>()
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
thumb?.also { t ->
gestureExclusionRects += t.copyBounds()
}
}
- Chiama
systemGestureExclusionRects()con gli elenchigestureExclusionRectsche crei.
Esamina il seguente esempio di codice di MySeekBar.kt:
private val gestureExclusionRects = mutableListOf<Rect>()
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
thumb?.also { t ->
gestureExclusionRects += t.copyBounds()
}
// Finally pass our updated list of rectangles to the system
systemGestureExclusionRects = gestureExclusionRects
}
- Chiama il metodo
updateGestureExclusion()daonDraw()oonLayout(). IgnoraonDraw()e aggiungi una chiamata aupdateGestureExclusion.
Esamina il seguente esempio di codice di MySeekBar.kt:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
updateGestureExclusion()
}
- Devi aggiornare i riferimenti
SeekBar. Per iniziare, aprifragment_nowplaying.xml. - Cambia
SeekBarincom.example.android.uamp.view.MySeekBar.
Esamina il seguente esempio di codice di fragment_nowplaying.xml:
<com.example.android.uamp.view.MySeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
- Per aggiornare i riferimenti
SeekBarinNowPlayingFragment.kt, apriNowPlayingFragment.kte modifica il tipo dipositionSeekBarinMySeekBar. Per far corrispondere il tipo di variabile, modifica i genericiSeekBarper la chiamatafindViewByIdinMySeekBar.
Esamina il seguente esempio di codice di NowPlayingFragment.kt:
val positionSeekBar: MySeekBar = view.findViewById<MySeekBar>(
R.id.seekBar
).apply { progress = 0 }
- Esegui l'app e interagisci con
SeekBar. Se i conflitti di gesture persistono, puoi sperimentare e modificare i limiti del pollice inMySeekBar. Fai attenzione a non creare una zona di esclusione dei gesti più grande del necessario, perché ciò limita altre potenziali chiamate di esclusione dei gesti e crea un comportamento incoerente per l'utente.
7. Complimenti
Complimenti! Hai imparato a evitare e risolvere i conflitti con i gesti di sistema.
Hai fatto in modo che la tua app utilizzi lo schermo intero quando hai esteso la visualizzazione edge-to-edge e hai utilizzato gli inset per allontanare i controlli dell'app dalle zone dei gesti. Hai anche imparato a disattivare il gesto Indietro del sistema sui controlli delle app.
Ora conosci i passaggi chiave necessari per far funzionare le tue app con i gesti di sistema.
Materiali aggiuntivi
- WindowInsets: listener per i layout
- Navigazione tramite gesti: da bordo a bordo
- Navigazione tramite gesti: gestione delle sovrapposizioni visive
- Navigazione tramite gesti: gestione dei conflitti tra gesti
- Garantire la compatibilità con la navigazione tramite gesti




