Untuk Android versi 10 atau lebih tinggi, gestur navigasi didukung sebagai mode baru. Ini memungkinkan aplikasi Anda menggunakan seluruh layar dan memberikan pengalaman tampilan yang lebih imersif. Saat pengguna menggeser ke atas dari tepi bawah layar, pengguna akan dibawa ke layar utama Android. Saat mereka menggeser ke dalam dari tepi kiri atau kanan, pengguna akan dibawa ke layar sebelumnya.
Dengan dua gestur ini, aplikasi Anda dapat memanfaatkan real estate layar di bagian bawah layar. Namun, jika aplikasi Anda menggunakan gestur atau memiliki kontrol di area gestur sistem, hal itu dapat menimbulkan konflik dengan gestur di seluruh sistem.
Codelab ini bertujuan untuk mengajari Anda cara menggunakan inset untuk menghindari konflik gestur. Selain itu, codelab ini bertujuan untuk mengajari Anda cara menggunakan API Pengecualian Gestur untuk kontrol, seperti handel geser, yang harus berada di zona gestur.
Yang akan Anda pelajari
- Cara menggunakan pemroses inset pada tampilan
- Cara menggunakan API Pengecualian Gestur
- Bagaimana mode imersif berperilaku saat gestur aktif
Codelab ini bertujuan untuk membuat aplikasi Anda kompatibel dengan Gestur Sistem. Konsep dan blok kode yang tidak relevan disamarkan dan disediakan untuk Anda salin dan tempel.
Yang akan Anda build
Universal Android Music Player (UAMP) adalah contoh aplikasi pemutar musik untuk Android yang ditulis dalam Kotlin. Anda akan menyiapkan UAMP untuk navigasi gestur.
- Gunakan inset untuk memindahkan kontrol dari area gestur
- Gunakan API Pengecualian Gestur untuk memilih keluar dari gestur kembali bagi kontrol yang mengalami konflik
- Gunakan build Anda untuk menjelajahi perubahan perilaku mode imersif dengan Navigasi Gestur
Yang akan Anda butuhkan
- Perangkat atau emulator yang menjalankan Android 10 atau lebih tinggi
- Android Studio
Universal Android Music Player (UAMP) adalah contoh aplikasi pemutar musik untuk Android yang ditulis dalam Kotlin. Aplikasi ini mendukung fitur yang mencakup pemutaran latar belakang, penanganan fokus audio, integrasi Asisten, dan berbagai platform seperti Wear, TV, dan Auto.
Gambar 1: Alur dalam UAMP
UAMP memuat katalog musik dari server jarak jauh dan memungkinkan pengguna untuk menemukan album dan lagu. Pengguna mengetuk lagu dan memutarnya melalui speaker atau headphone yang terhubung. Aplikasi ini tidak didesain untuk berfungsi dengan Gestur Sistem. Oleh karena itu, saat Anda menjalankan UAMP di perangkat yang menjalankan Android 10 atau lebih tinggi, Anda akan mengalami beberapa masalah pada awalnya.
Untuk mendapatkan aplikasi contoh, clone repositori dari GitHub dan alihkan ke cabang starter:
$ git clone https://github.com/googlecodelabs/android-gestural-navigation/
Atau, Anda dapat mendownload repositori sebagai file zip, mengekstraknya, dan membukanya di Android Studio.
Selesaikan langkah berikut:
- Buka dan build aplikasi di Android Studio.
- Buat perangkat virtual baru dan pilih API level 29. Atau, Anda dapat menghubungkan perangkat sesungguhnya yang menjalankan API level 29 atau lebih tinggi.
- Jalankan aplikasi. Daftar yang Anda lihat mengelompokkan lagu ke dalam pilihan Rekomendasi dan Album.
- Klik Rekomendasi dan pilih lagu dari daftar lagu.
- Aplikasi akan memulai pemutaran lagu.
Mengaktifkan Navigasi Gestur
Jika Anda menjalankan instance emulator baru dengan API level 29, Navigasi Gestur mungkin tidak diaktifkan secara default. Untuk mengaktifkan Navigasi Gestur, pilih Setelan sistem > Sistem > Navigasi Sistem > Navigasi Gestur.
Menjalankan aplikasi dengan Navigasi Gestur
Jika Anda menjalankan aplikasi dengan Navigasi Gestur yang diaktifkan dan memulai pemutaran lagu, Anda mungkin melihat kontrol pemutar sangat dekat dengan area gestur beranda dan kembali.
Apa itu tata letak layar penuh?
Aplikasi yang dijalankan di Android 10 atau lebih tinggi mampu menawarkan pengalaman layar dari tepi ke tepi sepenuhnya, terlepas dari diaktifkannya atau tidak gestur atau tombol untuk navigasi. Untuk menawarkan pengalaman tata letak layar penuh, aplikasi Anda harus menggambar di belakang menu navigasi dan status bar yang transparan.
Menggambar di belakang menu navigasi
Agar aplikasi Anda merender konten di bawah menu navigasi, Anda harus terlebih dahulu membuat latar belakang menu navigasi menjadi transparan. Kemudian, Anda harus membuat status bar transparan. Ini memungkinkan aplikasi Anda untuk menampilkan aplikasi sepanjang tinggi layar.
Untuk mengubah warna menu navigasi dan status bar, lakukan langkah-langkah berikut:
- Menu navigasi: Buka
res/values-29/styles.xml
dan setelnavigationBarColor
kecolor/transparent
. - Status bar: Setel juga
statusBarColor
kecolor/transparent
.
Tinjau contoh kode berikut dari 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>
Tanda visibilitas UI sistem
Anda juga harus menyetel tanda visibilitas UI sistem untuk memberi tahu sistem agar meletakkan aplikasi di bawah kolom sistem. API systemUiVisibility
di class View
memungkinkan Anda menyetel berbagai tanda. Lakukan langkah-langkah berikut:
- Buka class
MainActivity.kt
dan temukan metodeonCreate()
. Dapatkan instance darifragmentContainer
. - Setel hal berikut ini ke
content.systemUiVisibility
:
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Tinjau contoh kode berikut dari 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
Saat Anda menyetel tanda ini bersama-sama, Anda memberi tahu sistem bahwa Anda ingin aplikasi Anda ditampilkan satu layar penuh seolah-olah menu navigasi dan status bar tidak ada di sana. Lakukan langkah-langkah berikut:
- Jalankan aplikasi, dan untuk membuka layar pemutar, pilih lagu untuk diputar.
- Pastikan kontrol pemutar digambar di bawah menu navigasi, membuatnya sulit diakses:
- Buka Setelan sistem, beralih kembali ke mode navigasi tiga tombol, dan kembali ke aplikasi.
- Pastikan kontrol lebih sulit lagi digunakan dengan menu navigasi tiga tombol: Perhatikan bahwa
SeekBar
tersembunyi di belakang menu navigasi, dan tombol Putar/Jeda sebagian besar tertutup oleh menu navigasi. - Jelajahi dan bereksperimenlah sedikit. Setelah selesai, buka Setelan sistem dan alihkan kembali ke Navigasi Gestur:
Sekarang aplikasi menggambar tata letak layar penuh, tetapi ada masalah kegunaan, kontrol aplikasi yang mengalami konflik dan tumpang tindih, dan ini harus di-resolve.
WindowInsets
memberi tahu aplikasi tempat UI sistem muncul di atas konten Anda, bersama bagian layar yang lebih diprioritaskan Gestur Sistem dibandingkan gestur dalam aplikasi. Inset ditampilkan oleh class WindowInsets
danWindowInsetsCompat
di Jetpack. Kami sangat menyarankan Anda menggunakan WindowInsetsCompat
untuk mendapatkan perilaku yang konsisten di semua API level.
Inset sistem dan inset sistem wajib
API inset berikut adalah jenis inset yang paling biasa digunakan:
- Inset jendela sistem: Inset ini memberi tahu Anda tempat UI sistem ditampilkan di atas aplikasi Anda. Kami membahas cara Anda dapat menggunakan inset sistem untuk memindahkan kontrol Anda dari kolom sistem.
- Inset gestur sistem: Inset ini menampilkan semua area gestur. Kontrol geser dalam aplikasi apa pun di wilayah ini dapat memicu Gestur Sistem secara tidak sengaja.
- Inset gestur wajib: Inset ini adalah bagian dari inset gestur sistem dan tidak dapat diganti. Inset ini memberi tahu Anda area layar tempat perilaku Gestur Sistem akan selalu diprioritaskan daripada gestur dalam aplikasi.
Menggunakan inset untuk memindahkan kontrol aplikasi
Setelah tahu lebih banyak tentang API inset, Anda dapat memperbaiki kontrol aplikasi, seperti yang dijelaskan dalam langkah-langkah berikut:
- Dapatkan instance
playerLayout
dari instance objekview
. - Tambahkan
OnApplyWindowInsetsListener
keplayerView
. - Pindahkan tampilan dari area gestur: Temukan nilai inset sistem untuk bagian bawah dan tingkatkan padding tampilan dengan jumlah tersebut. Untuk mengupdate padding tampilan yang sesuai, ke [nilai yang terkait dengan padding bawah aplikasi], tambahkan [nilai yang terkait dengan nilai bawah inset sistem].
Tinjau contoh kode NowPlayingFragment.kt
berikut:
playerView = view.findViewById(R.id.playerLayout)
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(
bottom = insets.systemWindowInsetBottom + view.paddingBottom
)
insets
}
- Jalankan aplikasi dan pilih lagu. Perhatikan bahwa tampaknya tidak ada yang berubah dalam kontrol pemutar. Jika Anda menambahkan titik henti sementara dan menjalankan aplikasi dalam debug, Anda akan melihat bahwa pemroses tidak dipanggil.
- Untuk memperbaikinya, alihkan ke
FragmentContainerView
, yang menangani masalah ini secara otomatis. Bukaactivity_main.xml
dan ubahFrameLayout
keFragmentContainerView
.
Tinjau contoh kode activity_main.xml
berikut:
<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"/>
- Jalankan kembali aplikasi dan buka layar pemutar. Kontrol pemutar bawah digeser dari area gestur bawah.
Kontrol aplikasi sekarang berfungsi dengan Navigasi Gestur, tetapi kontrol bergerak lebih dari yang diharapkan. Anda harus me-resolve ini.
Mempertahankan padding dan margin saat ini
Perhatikan bahwa kontrol pemutar bergerak ke atas setiap kali Anda beralih ke aplikasi lain atau membuka layar utama dan kembali ke aplikasi tanpa menutupnya.
Ini karena aplikasi memicu requestApplyInsets()
setiap kali aktivitas dimulai. Bahkan tanpa panggilan ini, WindowInsets
dapat dikirim beberapa kali kapan saja selama siklus proses tampilan.
InsetListener
saat ini di playerView
berfungsi dengan baik saat pertama kali Anda menambahkan jumlah nilai bawah inset ke nilai padding bawah aplikasi yang dideklarasikan di activity_main.xml
. Namun, panggilan berikutnya terus menambahkan nilai bawah inset ke padding bawah tampilan yang sudah diupdate.
Untuk mengatasinya, lakukan langkah-langkah berikut:
- Catat nilai padding tampilan awal. Buat val baru dan simpan nilai padding tampilan awal dari
playerView
, tepat sebelum kode pemroses.
Tinjau contoh kode NowPlayingFragment.kt
berikut:
val initialPadding = playerView.paddingBottom
- Gunakan nilai awal ini untuk mengupdate padding bawah tampilan, yang memungkinkan Anda untuk menghindari penggunaan nilai padding bawah aplikasi saat ini.
Tinjau contoh kode NowPlayingFragment.kt
berikut:
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(bottom = insets.systemWindowInsetBottom + initialPadding)
insets
}
- Jalankan kembali aplikasi. Beralihlah antar aplikasi dan buka layar utama. Saat Anda menampilkan aplikasi, kontrol pemutar berada tepat di atas area gestur.
Mendesain ulang kontrol aplikasi
Seekbar pemutar terlalu dekat dengan area gestur bawah, yang berarti pengguna dapat memicu gestur beranda secara tidak sengaja ketika mereka melakukan gerakan geser horizontal. Jika Anda meningkatkan padding lebih banyak lagi, ini dapat mengatasi masalah, tetapi juga dapat memindahkan pemutar lebih tinggi dari yang diinginkan.
Penggunaan inset memungkinkan Anda memperbaiki konflik gestur, tetapi terkadang dengan perubahan desain kecil, Anda dapat menghindari konflik gestur sepenuhnya. Untuk mendesain ulang kontrol pemutar guna menghindari konflik gestur, lakukan langkah-langkah berikut:
- Buka
fragment_nowplaying.xml
. Beralih ke Tampilan desain dan pilihSeekBar
di bagian paling bawah:
- Beralih ke Tampilan kode.
- Untuk memindahkan
SeekBar
ke bagian atasplayerLayout
, ubahlayout_constraintTop_toBottomOf
SeekBar keparent
. - Untuk membatasi item lain di
playerView
ke bagian bawahSeekBar
, ubahlayout_constraintTop_toTopOf
dari induk ke@+id/seekBar
padamedia_button
,title
, danposition
.
Tinjau contoh kode fragment_nowplaying.xml
berikut:
<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>
- Jalankan aplikasi dan berinteraksi dengan pemutar dan seekbar.
Perubahan kecil pada desain ini meningkatkan aplikasi secara signifikan.
Kontrol pemutar untuk konflik gestur di area gestur beranda telah diperbaiki. Area gestur kembali juga dapat memicu konflik dengan kontrol aplikasi. Screenshot berikut menunjukkan bahwa seekbar pemutar saat ini berada di area gestur kembali kanan dan kiri:
SeekBar
menangani konflik gestur secara otomatis. Namun, Anda mungkin perlu menggunakan komponen UI lain yang memicu konflik gestur. Dalam kasus ini, Anda dapat menggunakan Gesture Exclusion API
untuk keluar sebagian dari gestur kembali.
Menggunakan API Pengecualian Gestur
Untuk membuat zona pengecualian gestur, panggil setSystemGestureExclusionRects()
pada tampilan Anda dengan daftar objek rect
. Objek rect
ini memetakan ke koordinat area persegi panjang yang dikecualikan. Panggilan ini harus dilakukan di metode onLayout()
atau onDraw()
tampilan. Caranya, lakukan langkah-langkah berikut:
- Buat paket baru bernama
view
. - Untuk memanggil API ini, buat class baru bernama
MySeekBar
dan perluasAppCompatSeekBar
.
Tinjau contoh kode MySeekBar.kt
berikut:
class MySeekBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = android.R.attr.seekBarStyle
) : androidx.appcompat.widget.AppCompatSeekBar(context, attrs, defStyle) {
}
- Buat metode baru bernama
updateGestureExclusion()
.
Tinjau contoh kode MySeekBar.kt
berikut:
private fun updateGestureExclusion() {
}
- Tambahkan tanda centang untuk melewati panggilan ini pada API level 28 atau lebih rendah.
Tinjau contoh kode MySeekBar.kt
berikut:
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
}
- Karena API Pengecualian Gestur memiliki batas 200 dp, hanya kecualikan thumb seekbar saja. Dapatkan salinan batas seekbar dan tambahkan setiap objek ke daftar yang dapat diubah.
Tinjau contoh kode MySeekBar.kt
berikut:
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()
}
}
- Panggil
systemGestureExclusionRects()
dengan daftargestureExclusionRects
yang Anda buat.
Tinjau contoh kode MySeekBar.kt
berikut:
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
}
- Panggil metode
updateGestureExclusion()
darionDraw()
atauonLayout()
. GantionDraw()
dan tambahkan panggilan keupdateGestureExclusion
.
Tinjau contoh kode MySeekBar.kt
berikut:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
updateGestureExclusion()
}
- Anda harus mengupdate referensi
SeekBar
. Untuk memulai, bukafragment_nowplaying.xml
. - Ubah
SeekBar
menjadicom.example.android.uamp.view.MySeekBar
.
Tinjau contoh kode fragment_nowplaying.xml
berikut:
<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" />
- Untuk mengupdate referensi
SeekBar
diNowPlayingFragment.kt
, bukaNowPlayingFragment.kt
dan ubah jenispositionSeekBar
keMySeekBar
. Untuk mencocokkan jenis variabel, ubah generikSeekBar
untuk panggilanfindViewById
keMySeekBar
.
Tinjau contoh kode NowPlayingFragment.kt
berikut:
val positionSeekBar: MySeekBar = view.findViewById<MySeekBar>(
R.id.seekBar
).apply { progress = 0 }
- Jalankan aplikasi dan berinteraksi dengan
SeekBar
. Jika Anda masih mengalami konflik gestur, Anda dapat bereksperimen dan memodifikasi batas thumb diMySeekBar
. Berhati-hatilah agar tidak membuat zona pengecualian gestur lebih besar dari yang diperlukan, karena ini akan membatasi panggilan pengecualian gestur potensial lainnya, dan menciptakan perilaku yang tidak konsisten bagi pengguna.
Selamat! Anda telah mempelajari cara menghindari dan mengatasi konflik dengan Gestur Sistem!
Anda membuat aplikasi menggunakan layar penuh ketika Anda memperluas dari tepi ke tepi dan menggunakan inset untuk memindahkan kontrol aplikasi dari zona gestur. Anda juga telah mempelajari cara menonaktifkan gestur kembali sistem pada kontrol aplikasi.
Sekarang Anda telah mengetahui langkah-langkah penting yang diperlukan untuk membuat aplikasi Anda berfungsi dengan Gestur Sistem!
Materi tambahan
- WindowInsets — Pemroses ke tata letak
- Navigasi Gestur: Menampilkan tata letak layar penuh
- Navigasi Gestur: Menangani visual yang tumpang-tindih
- Navigasi Gestur: Menangani konflik gestur
- Memastikan kompatibilitas dengan navigasi gestur