1. Introduzione
Per Android 10 o versioni successive, i gesti di navigazione sono supportati come una 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 indirizzato alla schermata Home di Android. Quando l'utente scorre verso l'interno dai bordi sinistro o destro, l'utente viene indirizzato alla schermata precedente.
Con questi due gesti, la tua app può sfruttare lo spazio che occupa nella parte inferiore dello schermo. Tuttavia, se l'app utilizza i gesti o dispone di controlli nelle aree di sistema relative ai gesti, potrebbero verificarsi conflitti con i gesti a livello di sistema.
Questo codelab ha lo scopo di insegnarti a usare i riquadri per evitare conflitti di gesti. Inoltre, questi codelab hanno lo scopo di insegnarti a utilizzare l'API Gesto Esclusione per i controlli, come i punti di manipolazione di trascinamento, che devono trovarsi nelle zone dei gesti.
Cosa imparerai
- Come utilizzare gli ascoltatori inseriti nelle visualizzazioni
- Come utilizzare l'API Empower Exclusion
- Comportamento della modalità immersiva quando i gesti sono attivi
Questo codelab ha lo scopo di rendere la tua app compatibile con i gesti di sistema. Concetti irrilevanti e blocchi di codice sono trattati solo superficialmente e sono disponibili per essere copiati e incollati.
Cosa creerai
L'Universal Android Music Player (UAMP) è un'app di esempio di lettore musicale per Android scritta in Kotlin. Configurerai UAMP per la navigazione gestuale.
- Usa i riquadri per allontanare i controlli dalle aree dei gesti
- Utilizza l'API Driven Exclusion per disattivare il gesto Indietro per i controlli in conflitto
- Usa le tue build per esplorare i cambiamenti del 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
L'Universal Android Music Player (UAMP) è un'app di esempio di musica per Android scritta in Kotlin. Supporta funzionalità che includono riproduzione in background, gestione del focus audio, integrazione con l'assistente e diverse 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 album e brani. L'utente tocca un brano e questo viene riprodotto tramite altoparlanti o cuffie connessi. L'app non è progettata per supportare i Gesti di sistema. Pertanto, quando esegui UAMP su un dispositivo con Android 10 o versioni successive, inizialmente riscontri alcuni problemi.
3. Configurazione
Per scaricare l'app di esempio, clona il repository da GitHub e passa al ramo 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 connettere un dispositivo reale con livello API 29 o superiore.
- Esegui l'app. L'elenco visualizzato raggruppa i brani nelle selezioni Consigliati e Album.
- Fai clic su Consigliati e seleziona un brano dall'elenco.
- L'app avvia la riproduzione del brano.
Attiva la navigazione tramite gesti
Se esegui una nuova istanza dell'emulatore con livello API 29, la navigazione tramite gesti potrebbe non essere attiva 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 con i gesti della schermata Home e Indietro.
4. Vai a livello perimetrale
Che cos'è edge-to-edge?
Le app eseguite su Android 10 o versioni successive possono offrire un'esperienza con lo schermo completa, 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 visualizzate dietro le barre di navigazione e di stato trasparenti.
Disegna dietro la barra di navigazione
Affinché l'app visualizzi i contenuti sotto la barra di navigazione, devi prima rendere trasparente lo sfondo della barra di navigazione. Devi quindi rendere trasparente la barra di stato. In questo modo la tua app potrà visualizzare la tua app per l'intera altezza dello schermo.
Per modificare il colore della barra di navigazione e della barra di stato, procedi nel seguente modo:
- Barra di navigazione: apri
res/values-29/styles.xml
e impostanavigationBarColor
sucolor/transparent
. - Barra di stato: allo stesso modo, imposta
statusBarColor
sucolor/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à dell'UI di sistema
Devi anche impostare i flag di visibilità dell'interfaccia utente di sistema per indicare al sistema di sovrapporre l'app sotto le barre di sistema. Le API systemUiVisibility
nella classe View
consentono di impostare una serie di flag. Procedi nel seguente modo:
- Apri il corso
MainActivity.kt
e trova il metodoonCreate()
. Recupera un'istanza difragmentContainer
. - Imposta il seguente valore su
content.systemUiVisibility
:
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.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 insieme questi flag, 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. Procedi nel seguente modo:
- Esegui l'app e, per andare alla schermata del player, seleziona un brano da riprodurre.
- Verifica che i controlli del player siano tracciati sotto la barra di navigazione; in questo modo è difficile accedervi:
- 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 utilizzare con la barra di navigazione con tre pulsanti: tieni presente che
SeekBar
è nascosto dietro la barra di navigazione e che la funzione Riproduci/Pausa è coperta in gran parte dalla barra di navigazione. - Esplora e sperimenta. Al termine, vai alle Impostazioni di sistema e torna alla Navigazione tramite gesti:
L'app è ora in modalità edge-to-edge, ma sono presenti problemi di usabilità e controlli delle app che sono in conflitto e che si sovrappongono e questi problemi devono essere risolti.
5. Inserti
WindowInsets
indica all'app dove viene visualizzata l'UI di sistema nella parte superiore dei contenuti e quali aree dello schermo hanno la priorità sui gesti in-app. Gli insiemi sono rappresentati dalla classe WindowInsets
e dalla classe WindowInsetsCompat
in Jetpack. Ti consigliamo vivamente di utilizzare WindowInsetsCompat
per avere un comportamento coerente in tutti i livelli API.
Inserisci di sistema e riquadri di sistema obbligatori
Le seguenti API integrate sono i tipi di inserti più utilizzati:
- Riquadri delle finestre di sistema:indicano dove viene visualizzata l'UI di sistema sopra l'app. Abbiamo parlato di come utilizzare gli insiemi di sistema per allontanare i controlli dalle barre del sistema.
- Elementi con gesti di sistema:restituiscono tutte le aree corrispondenti. Tutti i controlli di scorrimento in-app in queste aree possono attivare accidentalmente i Gesti di sistema.
- Inserti di gesti obbligatori:sono un sottoinsieme degli insiemi di 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.
Usare i riquadri per spostare i controlli delle app
Ora che conosci meglio le API integrate, puoi correggere i controlli dell'app, come descritto nei seguenti passaggi:
- Recupera un'istanza di
playerLayout
dall'istanza dell'oggettoview
. - Aggiungi una
OnApplyWindowInsetsListener
aplayerView
. - Allontana la visualizzazione dall'area del gesto: trova il valore del riquadro di sistema per la parte inferiore e aumenta la spaziatura interna della visualizzazione di questo valore. Per aggiornare di conseguenza la spaziatura interna della vista a [valore associato alla spaziatura interna inferiore dell'app], aggiungi [il valore associato al valore inferiore inserito nel 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. Tieni presente che non sembra cambiare nulla nei controlli del player. Se aggiungi un punto di interruzione ed esegui l'app nel debug, vedrai che il listener non è stato chiamato.
- Per risolvere il problema, passa all'app
FragmentContainerView
, che gestisce il problema automaticamente. Apriactivity_main.xml
e cambiaFrameLayout
inFragmentContainerView
.
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 in basso del player sono spostati dall'area dei gesti in basso.
I controlli dell'app ora funzionano con la navigazione tramite gesti, ma si muovono più del previsto. Devi risolvere questo problema.
Mantenere la spaziatura interna e i margini correnti
Se passi ad altre app o apri la schermata Home e torni all'app senza chiuderla, tieni presente che i controlli del player vengono spostati verso l'alto ogni volta.
Il motivo è che l'app attiva requestApplyInsets()
ogni volta che viene avviata l'attività. Anche senza questa chiamata, WindowInsets
può essere inviato più volte e in qualsiasi momento durante il ciclo di vita di una vista.
L'attuale InsetListener
in playerView
funziona perfettamente la prima volta quando aggiungi il valore minimo inserito al valore di spaziatura interna inferiore dell'app dichiarato in activity_main.xml
. Nelle chiamate successive, tuttavia, continua ad aggiungere il valore inferiore del riquadro alla spaziatura interna inferiore della vista già aggiornata.
Per risolverlo, segui questi passaggi:
- Registra il valore di spaziatura interna della visualizzazione iniziale. Crea un nuovo valore e memorizza il valore di spaziatura interna della visualizzazione iniziale pari a
playerView
, subito prima del codice listener.
Esamina il seguente esempio di codice di NowPlayingFragment.kt
:
val initialPadding = playerView.paddingBottom
- Utilizza questo valore iniziale per aggiornare la spaziatura interna inferiore della visualizzazione, in modo da evitare di utilizzare il valore attuale di spaziatura interna 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. Spostati tra le app e vai alla schermata Home. Quando torni nell'app, i controlli del player si trovano subito sopra l'area del gesto.
Riprogettare i controlli dell'app
La barra di scorrimento del player è troppo vicina all'area in basso del gesto, il che significa che l'utente può attivare accidentalmente il gesto Home quando esegue uno scorrimento orizzontale. Se aumenti ulteriormente la spaziatura interna, il problema potrebbe risolversi, ma il player potrebbe anche essere spostato più in alto del previsto.
L'uso dei riquadri consente di risolvere i conflitti di gesti, ma a volte, con piccole modifiche al design, puoi evitare del tutto questi conflitti. Per riprogettare i controlli del player ed evitare conflitti di gesti, procedi nel seguente modo:
- Apri
fragment_nowplaying.xml
. Passa alla Vista struttura e selezionaSeekBar
in basso:
- Passa alla Vista codice.
- Per spostare
SeekBar
in cima allaplayerLayout
, modificalayout_constraintTop_toBottomOf
della SeekBar inparent
. - Per vincolare altri elementi in
playerView
alla parte inferiore diSeekBar
, modificalayout_constraintTop_toTopOf
da principale a@+id/seekBar
sumedia_button
,title
eposition
.
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 scorrimento.
Queste modifiche minime al design migliorano notevolmente l'app.
6. API Gesti Esclusione
I controlli del player per i conflitti di gesti nell'area dei gesti della schermata Home sono fissi. L'area del gesto Indietro può anche creare conflitti con i controlli dell'app. Il seguente screenshot mostra che la barra di scorrimento del player si trova attualmente nell'area dei gesti Indietro a destra e a sinistra:
SeekBar
gestisce automaticamente i conflitti di gesti. Tuttavia, potresti dover utilizzare altri componenti dell'interfaccia utente che attivano conflitti di gesti. In questi casi, puoi utilizzare Gesture Exclusion API
per disattivare parzialmente il gesto Indietro.
Utilizzare l'API MANAGE Exclusion
Per creare una zona di esclusione dei gesti, chiama setSystemGestureExclusionRects()
nella tua vista con un elenco di rect
oggetti. Questi rect
oggetti vengono mappati alle coordinate delle aree rettangolari escluse. Questa chiamata deve essere eseguita nei metodi onLayout()
o onDraw()
della vista. Per farlo, segui questi passaggi:
- Crea un nuovo pacchetto denominato
view
. - Per chiamare questa API, crea un nuovo corso denominato
MySeekBar
ed 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 saltare questa chiamata con livello API 28 o precedente.
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 Gesti Esclusione ha un limite di 200 dp, escludi solo il pollice della barra di scorrimento. Recupera una copia dei limiti della barra di scorrimento e aggiungi ciascun 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 elenchigestureExclusionRects
che hai creato.
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()
. Esegui l'override dionDraw()
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
SeekBar
incom.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
SeekBar
inNowPlayingFragment.kt
, apriNowPlayingFragment.kt
e modifica il tipo dipositionSeekBar
inMySeekBar
. Per far corrispondere il tipo di variabile, modifica i genericiSeekBar
per la chiamatafindViewById
inMySeekBar
.
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 l'
SeekBar
. Se continui a riscontrare conflitti di gesti, 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é questo 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 usare la tua app a schermo intero estendendo l'area da bordo a bordo e utilizzato gli riquadri per allontanare i controlli delle app dalle zone dei gesti. Hai anche imparato a disattivare il gesto Indietro del sistema nei controlli delle app.
Ora sai quali sono i passaggi chiave necessari per far funzionare le tue app con i Gesti di sistema.
Materiali aggiuntivi
- WindowInsets — Listener di layout
- Navigazione tramite gesti: edge-to-edge
- Navigazione tramite gesti: gestire le sovrapposizioni visive
- Navigazione tramite gesti: gestione dei conflitti dei gesti
- Garantire la compatibilità con la navigazione tramite gesti