1. Wprowadzenie
W Androidzie 10 i nowszym gesty nawigacyjne są obsługiwane jako nowy tryb. Dzięki temu aplikacja może korzystać z całego ekranu i zapewniać lepsze wrażenia podczas wyświetlania. Gdy użytkownik przesunie palcem od dolnej krawędzi ekranu w górę, przejdzie na ekran główny Androida. Gdy użytkownik przesunie palcem do wewnątrz od lewej lub prawej krawędzi, wróci do poprzedniego ekranu.
Dzięki tym dwóm gestom aplikacja może wykorzystać przestrzeń u dołu ekranu. Jeśli jednak Twoja aplikacja używa gestów lub ma elementy sterujące w obszarach gestów systemowych, może to powodować konflikty z gestami systemowymi.
W tym ćwiczeniu w Codelabs dowiesz się, jak używać wcięć, aby uniknąć konfliktów gestów. Ten samouczek ma też na celu nauczenie Cię, jak używać interfejsu API wykluczania gestów w przypadku elementów sterujących, takich jak uchwyty przeciągania, które muszą znajdować się w strefach gestów.
Czego się nauczysz
- Jak używać detektorów wstawiania w przypadku widoków
- Jak korzystać z interfejsu Gesture Exclusion API
- Jak tryb pełnoekranowy działa, gdy gesty są aktywne
Te ćwiczenia z programowania mają na celu dostosowanie aplikacji do gestów systemowych. Nieistotne koncepcje i bloki kodu zostały pominięte. Można je po prostu skopiować i wkleić.
Co utworzysz
Uniwersalny odtwarzacz muzyki na Androida (UAMP) to przykładowa aplikacja do odtwarzania muzyki na Androida napisana w Kotlinie. Skonfigurujesz UAMP do nawigacji przy użyciu gestów.
- Używaj wcięć, aby odsunąć elementy sterujące od obszarów gestów
- Używanie interfejsu Gesture Exclusion API do rezygnacji z gestu wstecz w przypadku elementów sterujących, które powodują konflikt
- Sprawdzanie zmian w trybie immersyjnym w przypadku nawigacji gestami
Czego potrzebujesz
- urządzenie lub emulator z Androidem 10 lub nowszym,
- Android Studio
2. Aplikacje ogółem
Uniwersalny odtwarzacz muzyki na Androida (UAMP) to przykładowa aplikacja do odtwarzania muzyki na Androida napisana w Kotlinie. Obsługuje funkcje takie jak odtwarzanie w tle, zarządzanie aktywnością audio, integracja z Asystentem i wiele platform, np. Wear, TV i Auto.
|
|
|
Rysunek 1.: przepływ w UAMP
UAMP wczytuje katalog muzyczny z serwera zdalnego i umożliwia użytkownikowi przeglądanie albumów i utworów. Użytkownik klika utwór, który jest odtwarzany przez podłączone głośniki lub słuchawki. Aplikacja nie jest przeznaczona do współpracy z gestami systemowymi. Dlatego podczas uruchamiania UAMP na urządzeniu z Androidem 10 lub nowszym początkowo możesz napotkać pewne problemy.
3. Konfiguracja
Aby pobrać przykładową aplikację, sklonuj repozytorium z GitHuba i przejdź do gałęzi starter:
$ git clone https://github.com/googlecodelabs/android-gestural-navigation/
Możesz też pobrać repozytorium jako plik ZIP, rozpakować go i otworzyć w Android Studio.
Wykonaj te czynności:
- Otwórz i skompiluj aplikację w Android Studio.
- Utwórz nowe urządzenie wirtualne i wybierz poziom interfejsu API 29. Możesz też podłączyć prawdziwe urządzenie z interfejsem API na poziomie 29 lub nowszym.
- Uruchom aplikację. Na wyświetlonej liście utwory są pogrupowane w sekcjach Polecane i Albumy.
- Kliknij Polecane i wybierz utwór z listy.
- Aplikacja rozpocznie odtwarzanie utworu.
Włączanie nawigacji przy użyciu gestów
Jeśli uruchomisz nową instancję emulatora z poziomem interfejsu API 29, nawigacja gestami może nie być domyślnie włączona. Aby włączyć nawigację przy użyciu gestów, wybierz Ustawienia systemu > System > Nawigacja w systemie > Nawigacja przy użyciu gestów.
Uruchamianie aplikacji za pomocą nawigacji przy użyciu gestów
Jeśli uruchomisz aplikację z włączonym sterowaniem gestami i rozpoczniesz odtwarzanie utworu, możesz zauważyć, że elementy sterujące odtwarzacza znajdują się bardzo blisko obszarów gestów powrotu na ekran główny i wstecz.
4. Wyświetlanie bez ramki
Co to jest wyświetlacz od krawędzi do krawędzi?
Aplikacje działające na Androidzie 10 lub nowszym mogą oferować pełny ekran bez ramki, niezależnie od tego, czy do nawigacji są włączone gesty czy przyciski. Aby zapewnić wyświetlanie od krawędzi do krawędzi, aplikacje muszą być rysowane za przezroczystymi paskami nawigacyjnymi i paskami stanu.
Rysuj za paskiem nawigacyjnym
Aby aplikacja mogła renderować treści pod paskiem nawigacyjnym, musisz najpierw ustawić przezroczyste tło paska nawigacyjnego. Następnie musisz ustawić przezroczystość paska stanu. Dzięki temu aplikacja może wyświetlać się na całej wysokości ekranu.
Aby zmienić kolor paska nawigacyjnego i paska stanu:
- Pasek nawigacyjny: otwórz
res/values-29/styles.xmli ustawnavigationBarColornacolor/transparent. - Pasek stanu: podobnie ustaw
statusBarColornacolor/transparent.
Zapoznaj się z poniższym przykładowym kodem 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>
Flagi widoczności interfejsu systemu
Musisz też ustawić flagi widoczności interfejsu systemu, aby poinformować system, że aplikacja ma być wyświetlana pod paskami systemowymi. Interfejsy API systemUiVisibility w klasie View umożliwiają ustawianie różnych flag. Wykonaj te czynności:
- Otwórz klasę
MainActivity.kti znajdź metodęonCreate(). Uzyskaj instancjęfragmentContainer. - Ustaw te opcje na
content.systemUiVisibility:
View.SYSTEM_UI_FLAG_LAYOUT_STABLEView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREENView.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Zapoznaj się z poniższym przykładowym kodem 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
Ustawiając te flagi razem, informujesz system, że chcesz, aby aplikacja była wyświetlana na pełnym ekranie, tak jakby nie było pasków nawigacji i stanu. Wykonaj te czynności:
- Uruchom aplikację i wybierz utwór, aby przejść do ekranu odtwarzacza.
- Sprawdź, czy elementy sterujące odtwarzacza są wyświetlane pod paskiem nawigacyjnym, co utrudnia do nich dostęp:
|
|
- Otwórz ustawienia systemu, wróć do trybu nawigacji za pomocą 3 przycisków i ponownie otwórz aplikację.
- Sprawdź, czy sterowanie jest jeszcze trudniejsze w przypadku paska nawigacyjnego z 3 przyciskami: zauważ, że
SeekBarjest ukryty za paskiem nawigacyjnym, a przycisk Odtwórz/Wstrzymaj jest w większości zasłonięty przez pasek nawigacyjny. - Eksperymentuj. Gdy skończysz, otwórz ustawienia systemu i przywróć nawigację przy użyciu gestów:

Aplikacja jest teraz wyświetlana na całej powierzchni ekranu, ale występują problemy z łatwością obsługi i konflikty między elementami sterującymi, które się nakładają. Musisz je rozwiązać.
5. Wstawki
WindowInsets informują aplikację, gdzie interfejs systemu pojawia się nad treścią, a także w których regionach ekranu gesty systemowe mają priorytet przed gestami w aplikacji. W Jetpacku wstawki są reprezentowane przez klasę WindowInsets i klasę WindowInsetsCompat. Zdecydowanie zalecamy używanie WindowInsetsCompat, aby zapewnić spójne działanie na wszystkich poziomach interfejsu API.
Wstawki systemowe i obowiązkowe wstawki systemowe
Najczęściej używane typy wstawki to te interfejsy API:
- Odcięcia okna systemowego: informują, gdzie interfejs systemu jest wyświetlany nad aplikacją. Wyjaśniamy, jak używać odcięć systemowych, aby odsunąć elementy sterujące od pasków systemowych.
- Wcięcia gestów systemowych: zwracają wszystkie obszary gestów. W tych regionach gesty wykonywane w aplikacjach mogą przypadkowo uruchamiać gesty systemowe.
- Obowiązkowe wcięcia na gesty: są podzbiorem wcięć na gesty systemowe i nie można ich zastąpić. Wskazują one obszary ekranu, w których gesty systemowe mają zawsze wyższy priorytet niż gesty w aplikacji.
Używanie wstawek do przenoszenia elementów sterujących aplikacją
Teraz, gdy wiesz już więcej o interfejsach API wstawki, możesz naprawić elementy sterujące aplikacji, wykonując czynności opisane poniżej:
- Pobierz instancję
playerLayoutz instancji obiektuview. - Dodaj
OnApplyWindowInsetsListenerdoplayerView. - Przesuń widok z obszaru gestów: znajdź wartość wstawki systemowej u dołu i zwiększ o tę wartość dopełnienie widoku. Aby odpowiednio zaktualizować dopełnienie widoku, do [wartości powiązanej z dopełnieniem u dołu aplikacji] dodaj [wartość powiązaną z wartością wstawki systemowej u dołu].
Zapoznaj się z poniższym przykładowym kodem NowPlayingFragment.kt:
playerView = view.findViewById(R.id.playerLayout)
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(
bottom = insets.systemWindowInsetBottom + view.paddingBottom
)
insets
}
- Uruchom aplikację i wybierz utwór. Zauważ, że w elementach sterujących odtwarzacza nic się nie zmienia. Jeśli dodasz punkt przerwania i uruchomisz aplikację w trybie debugowania, zobaczysz, że detektor nie jest wywoływany.
- Aby to naprawić, przełącz się na
FragmentContainerView, które automatycznie rozwiązuje ten problem. Otwórzactivity_main.xmli zmieńFrameLayoutnaFragmentContainerView.
Zapoznaj się z poniższym przykładowym kodem 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"/>
- Uruchom ponownie aplikację i przejdź do ekranu odtwarzacza. Elementy sterujące odtwarzaczem u dołu są przesunięte od obszaru gestów u dołu.
Elementy sterujące aplikacji działają teraz z nawigacją gestami, ale poruszają się bardziej niż oczekiwano. Musisz rozwiązać ten problem.
Zachowaj bieżące dopełnienie i marginesy
Jeśli przełączysz się na inne aplikacje lub przejdziesz do ekranu głównego i wrócisz do aplikacji bez jej zamykania, zauważysz, że elementy sterujące odtwarzaczem za każdym razem przesuwają się w górę.
Dzieje się tak, ponieważ aplikacja wywołuje zdarzenie requestApplyInsets() za każdym razem, gdy rozpoczyna się aktywność. Nawet bez tego wywołania funkcja WindowInsets może być wysyłana wielokrotnie w dowolnym momencie cyklu życia widoku.
Obecny InsetListener na playerView działa prawidłowo za pierwszym razem, gdy dodasz wartość dolnego wcięcia do wartości dolnego dopełnienia aplikacji zadeklarowanej w activity_main.xml. Kolejne wywołania nadal dodają wartość dolnego wcięcia do dolnego dopełnienia już zaktualizowanego widoku.
Aby rozwiązać ten problem, wykonaj te czynności:
- Zapisz początkową wartość dopełnienia widoku. Utwórz nową zmienną val i zapisz w niej początkową wartość dopełnienia widoku
playerViewtuż przed kodem detektora.
Zapoznaj się z poniższym przykładowym kodem NowPlayingFragment.kt:
val initialPadding = playerView.paddingBottom
- Użyj tej wartości początkowej, aby zaktualizować dopełnienie u dołu widoku, co pozwoli Ci uniknąć używania bieżącej wartości dopełnienia u dołu aplikacji.
Zapoznaj się z poniższym przykładowym kodem NowPlayingFragment.kt:
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(bottom = insets.systemWindowInsetBottom + initialPadding)
insets
}
- Uruchom aplikację ponownie. Przełączaj się między aplikacjami i wracaj do ekranu głównego. Gdy wrócisz do aplikacji, elementy sterujące odtwarzaczem będą znajdować się tuż nad obszarem gestów.
Przeprojektowane elementy sterujące aplikacją
Pasek przewijania odtwarzacza znajduje się zbyt blisko obszaru gestów u dołu ekranu, co oznacza, że użytkownik może przypadkowo wywołać gest powrotu do ekranu głównego, gdy wykona przesunięcie w poziomie. Jeśli jeszcze bardziej zwiększysz margines, problem może zniknąć, ale odtwarzacz może zostać przesunięty wyżej, niż chcesz.
Użycie wstawek pozwala rozwiązać konflikty gestów, ale czasami dzięki niewielkim zmianom w projekcie można ich całkowicie uniknąć. Aby zmienić układ elementów sterujących odtwarzacza i uniknąć konfliktów gestów, wykonaj te czynności:
- Otwórz pokój
fragment_nowplaying.xml. Przełącz się na widok projektu i kliknijSeekBaru dołu:

- Przełącz na widok kodu.
- Aby przenieść
SeekBarna góręplayerLayout, zmieńlayout_constraintTop_toBottomOfw przypadku paska przewijania naparent. - Aby przyciągnąć inne elementy w
playerViewdo dołuSeekBar, zmieńlayout_constraintTop_toTopOfz wartości „parent” na@+id/seekBarw przypadku elementówmedia_button,titleiposition.
Zapoznaj się z poniższym przykładowym kodem 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>
- Uruchom aplikację i skorzystaj z odtwarzacza oraz paska przewijania.
Te niewielkie zmiany w projekcie znacznie poprawiają działanie aplikacji.
6. Gesture Exclusion API
Elementy sterujące odtwarzacza w przypadku konfliktów gestów w obszarze gestów domowych są stałe. Obszar gestu wstecz może też powodować konflikty z elementami sterującymi aplikacji. Poniższy zrzut ekranu pokazuje, że pasek przewijania odtwarzacza znajduje się obecnie w obszarach gestów wstecz po prawej i lewej stronie:

SeekBar automatycznie rozwiązuje konflikty gestów. Może jednak zajść potrzeba użycia innych komponentów interfejsu, które powodują konflikty gestów. W takich przypadkach możesz użyć Gesture Exclusion API, aby częściowo zrezygnować z gestu wstecz.
Korzystanie z interfejsu Gesture Exclusion API
Aby utworzyć strefę wykluczenia gestów, wywołaj metodę setSystemGestureExclusionRects() w widoku z listą obiektów rect. Te obiekty rect są mapowane na współrzędne wykluczonych obszarów prostokątnych. To wywołanie musi być wykonane w metodach onLayout() lub onDraw() widoku. Aby to zrobić, wykonaj te czynności:
- Utwórz nowy pakiet o nazwie
view. - Aby wywołać ten interfejs API, utwórz nową klasę o nazwie
MySeekBari rozszerzAppCompatSeekBar.
Zapoznaj się z poniższym przykładowym kodem MySeekBar.kt:
class MySeekBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = android.R.attr.seekBarStyle
) : androidx.appcompat.widget.AppCompatSeekBar(context, attrs, defStyle) {
}
- Utwórz nową metodę o nazwie
updateGestureExclusion().
Zapoznaj się z poniższym przykładowym kodem MySeekBar.kt:
private fun updateGestureExclusion() {
}
- Dodaj sprawdzenie, aby pominąć to wywołanie na poziomie API 28 lub niższym.
Zapoznaj się z poniższym przykładowym kodem MySeekBar.kt:
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
}
- Interfejs Gesture Exclusion API ma limit 200 dp, więc wyklucz tylko kciuk paska przewijania. Uzyskaj kopię granic paska przewijania i dodaj każdy obiekt do listy modyfikowalnej.
Zapoznaj się z poniższym przykładowym kodem 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()
}
}
- Zadzwoń do
systemGestureExclusionRects()za pomocą utworzonych przez siebie listgestureExclusionRects.
Zapoznaj się z poniższym przykładowym kodem 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
}
- Wywołaj metodę
updateGestureExclusion()zonDraw()lubonLayout(). ZastąponDraw()i dodaj połączenie doupdateGestureExclusion.
Zapoznaj się z poniższym przykładowym kodem MySeekBar.kt:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
updateGestureExclusion()
}
- Musisz zaktualizować odniesienia do
SeekBar. Aby rozpocząć, otwórzfragment_nowplaying.xml. - Zmień
SeekBarnacom.example.android.uamp.view.MySeekBar.
Zapoznaj się z poniższym przykładowym kodem 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" />
- Aby zaktualizować odwołania do
SeekBarwNowPlayingFragment.kt, otwórzNowPlayingFragment.kti zmień typpositionSeekBarnaMySeekBar. Aby dopasować typ zmiennej, zmień typy ogólneSeekBarw wywołaniufindViewByIdnaMySeekBar.
Zapoznaj się z poniższym przykładowym kodem NowPlayingFragment.kt:
val positionSeekBar: MySeekBar = view.findViewById<MySeekBar>(
R.id.seekBar
).apply { progress = 0 }
- Uruchom aplikację i kliknij
SeekBar. Jeśli nadal występują konflikty gestów, możesz eksperymentować i modyfikować granice kciuka wMySeekBar. Uważaj, aby nie tworzyć strefy wykluczenia gestów większej niż to konieczne, ponieważ ogranicza to inne potencjalne wywołania wykluczenia gestów i powoduje niespójne zachowanie dla użytkownika.
7. Gratulacje
Gratulacje! Wiesz już, jak unikać konfliktów z gestami systemowymi i jak je rozwiązywać.
Aplikacja korzysta z trybu pełnoekranowego, gdy rozszerzysz ją na cały ekran i użyjesz wstawek, aby przenieść elementy sterujące aplikacji poza strefy gestów. Dowiedziałeś się też, jak wyłączyć gest cofania w elementach sterujących aplikacji.
Znasz już najważniejsze kroki, które musisz wykonać, aby Twoje aplikacje działały z gestami systemowymi.
Dodatkowe materiały
- WindowInsets – słuchacze układów
- Nawigacja przy użyciu gestów: od krawędzi do krawędzi
- Nawigacja przy użyciu gestów: obsługa nakładających się elementów wizualnych
- Nawigacja przy użyciu gestów: rozwiązywanie konfliktów gestów
- Zapewnij zgodność z nawigacją przy użyciu gestów




