1. Wprowadzenie
Na urządzeniach z Androidem w wersji 10 lub nowszej gesty nawigacji są obsługiwane jako nowy tryb. Dzięki temu aplikacja może korzystać z pełnego ekranu i zapewnia bardziej atrakcyjne wrażenia. Gdy użytkownik przesunie palcem od dolnej krawędzi ekranu w górę, otworzy się ekran główny Androida. Gdy użytkownik przesunie palcem od lewej lub prawej krawędzi do środka, wyświetli się poprzedni ekran.
Dzięki tym dwóm gestom aplikacja może wykorzystać potencjał ekranu u dołu. Jeśli jednak aplikacja używa gestów lub zawiera elementy sterujące w obszarach gestów systemu, może to spowodować konflikt z gestami w całym systemie.
Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak używać wkładek, aby uniknąć konfliktów gestów. Dzięki tym ćwiczeniom w Codelabs dowiesz się, jak używać interfejsu Gesty Exclusion API do elementów sterujących (np. uchwytów przeciągania) w strefach gestów.
Czego dowiesz się
- Jak używać detektorów wstawionych w widokach
- Jak korzystać z interfejsu Step Exclusion API
- Jak działa tryb pojemny, gdy gesty są aktywne
Dzięki temu ćwiczeniu w Codelabs Twoja aplikacja będzie zgodna z gestami systemowymi. Nieistotne koncepcje i bloki kodu zostały zamaskowane i można je skopiować i wkleić.
Co utworzysz
Universal Android Music Player (UAMP) to przykładowa aplikacja odtwarzacza muzyki na Androida napisana w języku Kotlin. Skonfigurujesz usługę UAMP na potrzeby nawigacji przy użyciu gestów.
- Użyj wektorów, aby odsunąć elementy sterujące poza obszar gestów
- Aby zrezygnować z gestów cofania w przypadku elementów sterujących, które są w konflikcie, użyj interfejsu Gesty Exclusion API.
- Używaj swoich kompilacji, aby badać zmiany w trybie pojemnym za pomocą nawigacji przy użyciu gestów
Czego potrzebujesz
- urządzenia lub emulatora z Androidem 10 bądź nowszym,
- Android Studio,
2. Aplikacje ogółem
Universal Android Music Player (UAMP) to przykładowa aplikacja odtwarzacza muzyki na Androida napisana w języku Kotlin. Obsługuje ona funkcje takie jak odtwarzanie w tle, obsługa ostrości dźwięku, integracja z Asystentem oraz wiele platform, takich jak Wear, TV i Auto.
Rysunek 1. Przepływ w UAMP
UAMP wczytuje katalog muzyczny ze zdalnego serwera i umożliwia użytkownikowi przeglądanie albumów i utworów. Użytkownik klika utwór, a potem odtwarzany jest przez połączone głośniki lub słuchawki. Aplikacja nie jest przeznaczona do obsługi gestów systemowych. Dlatego gdy uruchomisz UAMP na urządzeniu z Androidem 10 lub nowszym, początkowo pojawią się pewne problemy.
3. Konfiguracja
Aby pobrać przykładową aplikację, skopiuj repozytorium z GitHuba i przełącz się do gałęzi starter:
$ git clone https://github.com/googlecodelabs/android-gestural-navigation/
Możesz też pobrać repozytorium jako plik ZIP, rozpakować je 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 API 29. Możesz też połączyć prawdziwe urządzenie z interfejsem API na poziomie 29 lub wyższym.
- Uruchom aplikację. Wyświetlana lista grupuje utwory w sekcjach Polecane i Albumy.
- Kliknij Polecane i wybierz utwór z listy.
- Aplikacja rozpocznie odtwarzanie utworu.
Włącz nawigację przy użyciu gestów
Jeśli uruchomisz nową instancję emulatora z interfejsem API poziomu 29, nawigacja przy użyciu gestów 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 przy użyciu nawigacji przy użyciu gestów
Jeśli uruchomisz aplikację z włączoną Nawigacją przy użyciu gestów i rozpoczniesz odtwarzanie utworu, możesz zauważyć, że elementy sterujące odtwarzaczem znajdują się bardzo blisko obszaru ekranu głównego i cofnięcia.
4. Od krawędzi do krawędzi
Co to jest profilowanie „od brzegu do krawędzi”?
Aplikacje na Androidzie 10 lub nowszym mogą wyświetlać cały ekran bez względu na to, czy nawigacja jest włączona za pomocą gestów czy przycisków. Aby aplikacje były jak najbardziej komfortowe, muszą znajdować się za przezroczystymi paskami stanu i nawigacją.
Rysowanie za paskiem nawigacyjnym
Aby aplikacja renderowała treść pod paskiem nawigacyjnym, musisz najpierw ustawić przezroczyste tło paska nawigacyjnego. Następnie pasek stanu musi być przezroczysty. W ten sposób aplikacja będzie wyświetlać się na całej wysokości ekranu.
Aby zmienić kolor paska nawigacyjnego i stanu, wykonaj następujące czynności:
- Pasek nawigacyjny: otwórz
res/values-29/styles.xml
i ustawnavigationBarColor
nacolor/transparent
. - Pasek stanu: w podobny sposób ustaw
statusBarColor
nacolor/transparent
.
Zapoznaj się z tym przykładowym kodem pliku 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 system umieścić aplikację pod paskami systemowymi. Interfejsy API systemUiVisibility
w klasie View
umożliwiają ustawianie różnych flag. Wykonaj te czynności:
- Otwórz klasę
MainActivity.kt
i znajdź metodęonCreate()
. Pobieram instancjęfragmentContainer
. - Ustaw wartość
content.systemUiVisibility
:
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Zapoznaj się z tym przykładowym kodem pliku 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
Gdy ustawisz te flagi, informujesz system, że aplikacja ma wyświetlać się na pełnym ekranie, tak jakby nie było na niej nawigacji ani pasków stanu. Wykonaj te czynności:
- Uruchom aplikację i przejdź do ekranu odtwarzacza, a potem wybierz utwór, który chcesz odtworzyć.
- Sprawdź, czy pod paskiem nawigacyjnym znajdują się elementy sterujące odtwarzaczem, co utrudnia dostęp:
- Przejdź do Ustawień systemu, wróć do trybu nawigacji przy użyciu 3 przycisków i wróć do aplikacji.
- Upewnij się, że korzystanie z paska nawigacyjnego z 3 przyciskami jest jeszcze trudniejsze. Zwróć uwagę, że przycisk
SeekBar
jest ukryty za paskiem nawigacyjnym, a funkcja Odtwórz/Wstrzymaj jest w większości zakryta przez pasek nawigacyjny. - Odkrywaj i eksperymentuj. Gdy skończysz, przejdź do ustawień systemu i wróć do Nawigacji przy użyciu gestów:
Aplikacja jest obecnie rysowana od krawędzi do krawędzi, ale występują problemy z obsługą, konflikty i nakładanie się – trzeba je rozwiązać.
5. Wcięcia
WindowInsets
informuje aplikację, w którym miejscu nad Twoimi treściami znajduje się interfejs systemu oraz które obszary ekranu Gesty systemowe mają wyższy priorytet niż gesty w aplikacji. Wstawki są reprezentowane przez klasę WindowInsets
i klasę WindowInsetsCompat
w Jetpack. Aby zapewnić spójne działanie na wszystkich poziomach interfejsu API, zdecydowanie zalecamy używanie interfejsu WindowInsetsCompat
.
Wstawki systemu i obowiązkowe wstawki systemu
Najczęściej używane typy wstawianych interfejsów API to:
- Wbudowane okna systemowe: wskazują, w którym miejscu nad aplikacją znajduje się interfejs systemu. Omówiliśmy, jak za pomocą wkładek systemowych odsunąć elementy sterujące od pasków systemowych.
- Wbudowane gesty systemowe: zwracają wszystkie obszary gestów. Wszelkie elementy sterujące przesuwaniem w aplikacji w tych regionach mogą przypadkowo aktywować gesty systemowe.
- Obowiązkowe wstawienia gestów: są podzbiorem wstawienia gestów systemowych i nie można ich zastąpić. Wskazują obszary ekranu, w których gesty systemowe mają zawsze wyższy priorytet niż gesty w aplikacji.
Używanie wkładek do przenoszenia elementów sterujących aplikacji
Teraz, gdy wiesz już więcej o interfejsach API wstawianych, możesz naprawić elementy sterujące aplikacji w sposób opisany poniżej:
- Pobierz instancję
playerLayout
z instancji obiektuview
. - Dodaj
OnApplyWindowInsetsListener
do:playerView
. - Odsuń widok poza obszar gestów: znajdź wartość systemową wstawki w dolnej części i zwiększ dopełnienie widoku o tę wartość. Aby odpowiednio zaktualizować dopełnienie widoku, dodaj do [wartość powiązaną z dopełnieniem na dole aplikacji] dodaj [wartość powiązaną z dolną wartością wstawienia systemu].
Zapoznaj się z tym przykładowym kodem pliku 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. Zwróć uwagę, że elementy sterujące odtwarzaczem nic się nie zmieniły. Jeśli dodasz punkt przerwania i uruchomisz aplikację w trybie debugowania, zauważysz, że detektor nie jest wywoływany.
- Aby rozwiązać ten problem, przełącz się na usługę
FragmentContainerView
, która automatycznie rozwiązuje ten problem. Otwórz aplikacjęactivity_main.xml
i zmieńFrameLayout
naFragmentContainerView
.
Zapoznaj się z tym przykładowym kodem pliku 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 aplikację jeszcze raz i przejdź do ekranu odtwarzacza. Elementy sterujące dolnym odtwarzaczem zostały przesuwane poza dolny obszar gestów.
Elementy sterujące aplikacji działają teraz z nawigacją przy użyciu gestów, ale elementy sterujące mogą być przesuwane bardziej niż oczekiwano. Musisz rozwiązać ten problem.
Zachowaj bieżące dopełnienie i marginesy
Jeśli przełączysz się na inną aplikację lub wrócisz do ekranu głównego, ale nie będziesz jej zamykać, elementy sterujące odtwarzaczem za każdym razem przesuwają się w górę.
Dzieje się tak, ponieważ aplikacja wyzwala requestApplyInsets()
przy każdym rozpoczęciu aktywności. Nawet bez tego wywołania element WindowInsets
może być wysyłany wiele razy w dowolnym momencie w cyklu życia wyświetlenia.
Bieżąca wartość InsetListener
w tabeli playerView
działa idealnie za pierwszym razem, gdy dodasz wartość dopełnienia dolnej części aplikacji zadeklarowaną w zasadzie activity_main.xml
. Kolejne wywołania będą jednak nadal dodawać wstawiona dolną wartość do już zaktualizowanego dolnego dopełnienia widoku.
Aby temu zapobiec, wykonaj te czynności:
- Zarejestruj początkową wartość dopełnienia widoku. Utwórz nową wartość i zapisz początkową wartość dopełnienia widoku danych
playerView
, tuż przed kodem detektora.
Zapoznaj się z tym przykładowym kodem pliku NowPlayingFragment.kt
:
val initialPadding = playerView.paddingBottom
- Za pomocą tej wartości początkowej zaktualizuj dopełnienie u dołu widoku, dzięki czemu unikniesz użycia bieżącej wartości dopełnienia u dołu aplikacji.
Zapoznaj się z tym przykładowym kodem pliku NowPlayingFragment.kt
:
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(bottom = insets.systemWindowInsetBottom + initialPadding)
insets
}
- Ponownie uruchom aplikację. Możesz przechodzić między aplikacjami i przejść do ekranu głównego. Po zwróceniu aplikacji elementy sterujące odtwarzaczem znajdują się tuż nad obszarem gestów.
Nowy wygląd ustawień aplikacji
Pasek przewijania w odtwarzaczu znajduje się zbyt blisko dolnego obszaru gestów, co oznacza, że użytkownik może przypadkowo uruchomić gest ekranu głównego, gdy przesunie w poziomie. Zwiększenie dopełnienia może rozwiązać problem, ale może też przesunąć odtwarzacz wyżej, niż chcesz.
Zastosowanie wstawienia pozwala rozwiązać konflikty gestów, ale czasami po wprowadzeniu niewielkich zmian w wyglądzie można całkowicie uniknąć konfliktu gestów. Aby przeprojektować elementy sterujące odtwarzaczem, by uniknąć konfliktów gestów, wykonaj te czynności:
- Otwórz pokój
fragment_nowplaying.xml
. Przełącz się na widok projektu i u dołu kliknijSeekBar
:
- Przełącz na widok kodu.
- Aby przenieść
SeekBar
na górę elementuplayerLayout
, zmieńlayout_constraintTop_toBottomOf
paska przewijania naparent
. - Aby ograniczyć inne elementy w elemencie
playerView
w dolnej części elementuSeekBar
, zmień ustawienielayout_constraintTop_toTopOf
z elementu nadrzędnego na@+id/seekBar
w systemachmedia_button
,title
iposition
.
Zapoznaj się z tym przykładowym kodem pliku 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 wejdź w interakcję z odtwarzaczem i paskiem przewijania.
Te drobne zmiany znacznie poprawiają działanie aplikacji.
6. Interfejs API wykluczania gestów
Naprawiono elementy sterujące odtwarzaczem w obszarze gestów w obszarze głównym. Obszar gestów cofania może też powodować konflikty z elementami sterującymi aplikacji. Na zrzucie ekranu widać, że pasek przewijania odtwarzacza znajduje się obecnie w obszarze gestów w prawej i lewej części ekranu:
SeekBar
automatycznie obsługuje konflikty gestów. Aby wyzwalać konflikty gestów, konieczne może być jednak użycie innych komponentów interfejsu. W takich przypadkach możesz użyć Gesture Exclusion API
, aby częściowo zrezygnować z gestu cofania.
Używanie interfejsu Gesty Exclusion API
Aby utworzyć strefę wykluczenia gestów, wywołaj w widoku setSystemGestureExclusionRects()
z listą obiektów rect
. Te rect
obiekty są mapowane na współrzędne wykluczonych obszarów prostokątnych. To wywołanie należy wykonać w metodach onLayout()
lub onDraw()
widoku danych. Aby to zrobić:
- Utwórz nowy pakiet o nazwie
view
. - Aby wywołać ten interfejs API, utwórz nową klasę o nazwie
MySeekBar
i rozszerz zakresAppCompatSeekBar
.
Zapoznaj się z tym przykładowym kodem pliku 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 tym przykładowym kodem pliku MySeekBar.kt
:
private fun updateGestureExclusion() {
}
- Dodaj zaznaczenie, aby pominąć to wywołanie, na poziomie API 28 lub niższym.
Zapoznaj się z tym przykładowym kodem pliku MySeekBar.kt
:
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
}
- Interfejs Gesty Exclusion API ma limit wynoszący 200 dp, dlatego pomijaj tylko kciuk paska przewijania. Pobierz kopię granic paska przewijania i dodaj każdy obiekt do listy zmiennych.
Zapoznaj się z tym przykładowym kodem pliku 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 użytkownika
systemGestureExclusionRects()
, korzystając z utworzonych listgestureExclusionRects
.
Zapoznaj się z tym przykładowym kodem pliku 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()
z numeruonDraw()
lubonLayout()
. ZastąponDraw()
i dodaj wywołanie doupdateGestureExclusion
.
Zapoznaj się z tym przykładowym kodem pliku MySeekBar.kt
:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
updateGestureExclusion()
}
- Musisz zaktualizować pliki referencyjne
SeekBar
. Najpierw otwórzfragment_nowplaying.xml
. - Zmień
SeekBar
nacom.example.android.uamp.view.MySeekBar
.
Zapoznaj się z tym przykładowym kodem pliku 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
SeekBar
wNowPlayingFragment.kt
, otwórzNowPlayingFragment.kt
i zmień typpositionSeekBar
naMySeekBar
. Aby dopasować typ zmiennej, zmień ogólną kategorięSeekBar
w wywołaniufindViewById
naMySeekBar
.
Zapoznaj się z tym przykładowym kodem pliku NowPlayingFragment.kt
:
val positionSeekBar: MySeekBar = view.findViewById<MySeekBar>(
R.id.seekBar
).apply { progress = 0 }
- Uruchom aplikację i wejdź w interakcję z
SeekBar
. Jeśli konflikty gestów nadal występują, możesz poeksperymentować i zmienić granice kciuka w usłudzeMySeekBar
. Uważaj, aby nie utworzyć strefy wykluczenia gestów szerszej, niż jest to konieczne, bo ogranicza to liczbę innych potencjalnych wykluczeń gestów i powoduje niespójne zachowanie użytkownika.
7. Gratulacje
Gratulacje! Już wiesz, jak unikać konfliktów i rozwiązywać konflikty za pomocą gestów systemowych.
Aplikacja przełącza się w tryb pełnego ekranu, gdy korzystasz z całkowitej krawędzi i używasz wkładek, aby odsunąć elementy sterujące aplikacji od stref gestów. Wiesz już też, jak wyłączyć systemowy gest cofania w elementach sterujących aplikacji.
Znasz już najważniejsze czynności, które musisz wykonać, aby korzystać z gestów systemowych w aplikacjach.
Materiały dodatkowe
- WindowInsets – detektory układów
- Nawigacja przy użyciu gestów: od krawędzi do krawędzi
- Nawigacja przy użyciu gestów: rozwiązanie problemu z nakładaniem się elementów wizualnych
- Nawigacja przy użyciu gestów: rozwiązywanie konfliktów gestów
- Zapewnianie zgodności dzięki nawigacji przy użyciu gestów