Navigasi Gestur dan pengalaman tata letak layar penuh

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.

Download zip

Selesaikan langkah berikut:

  1. Buka dan build aplikasi di Android Studio.
  2. Buat perangkat virtual baru dan pilih API level 29. Atau, Anda dapat menghubungkan perangkat sesungguhnya yang menjalankan API level 29 atau lebih tinggi.
  3. Jalankan aplikasi. Daftar yang Anda lihat mengelompokkan lagu ke dalam pilihan Rekomendasi dan Album.
  4. Klik Rekomendasi dan pilih lagu dari daftar lagu.
  5. 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:

  1. Menu navigasi: Buka res/values-29/styles.xml dan setel navigationBarColor ke color/transparent.
  2. Status bar: Setel juga statusBarColor ke color/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:

  1. Buka class MainActivity.kt dan temukan metode onCreate(). Dapatkan instance dari fragmentContainer.
  2. Setel hal berikut ini ke content.systemUiVisibility:

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:

  1. Jalankan aplikasi, dan untuk membuka layar pemutar, pilih lagu untuk diputar.
  2. Pastikan kontrol pemutar digambar di bawah menu navigasi, membuatnya sulit diakses:

  1. Buka Setelan sistem, beralih kembali ke mode navigasi tiga tombol, dan kembali ke aplikasi.
  2. 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.
  3. 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:

  1. Dapatkan instance playerLayout dari instance objek view.
  2. Tambahkan OnApplyWindowInsetsListener ke playerView.
  3. 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
}
  1. 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.
  2. Untuk memperbaikinya, alihkan ke FragmentContainerView, yang menangani masalah ini secara otomatis. Buka activity_main.xml dan ubah FrameLayout ke FragmentContainerView.

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"/>
  1. 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:

  1. 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
  1. 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
        }
  1. 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:

  1. Buka fragment_nowplaying.xml. Beralih ke Tampilan desain dan pilih SeekBar di bagian paling bawah:

  1. Beralih ke Tampilan kode.
  2. Untuk memindahkan SeekBar ke bagian atas playerLayout, ubah layout_constraintTop_toBottomOf SeekBar ke parent.
  3. Untuk membatasi item lain di playerView ke bagian bawah SeekBar, ubah layout_constraintTop_toTopOf dari induk ke @+id/seekBar pada media_button, title, dan position.

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>
  1. 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:

  1. Buat paket baru bernama view.
  2. Untuk memanggil API ini, buat class baru bernama MySeekBar dan perluas AppCompatSeekBar.

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) {

}
  1. Buat metode baru bernama updateGestureExclusion().

Tinjau contoh kode MySeekBar.kt berikut:

private fun updateGestureExclusion() {

}
  1. 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
}
  1. 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()
    }
}
  1. Panggil systemGestureExclusionRects() dengan daftar gestureExclusionRects 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
}
  1. Panggil metode updateGestureExclusion() dari onDraw() atau onLayout(). Ganti onDraw() dan tambahkan panggilan ke updateGestureExclusion.

Tinjau contoh kode MySeekBar.kt berikut:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    updateGestureExclusion()
}
  1. Anda harus mengupdate referensi SeekBar. Untuk memulai, buka fragment_nowplaying.xml.
  2. Ubah SeekBar menjadi com.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" />
  1. Untuk mengupdate referensi SeekBar di NowPlayingFragment.kt, buka NowPlayingFragment.kt dan ubah jenis positionSeekBar ke MySeekBar. Untuk mencocokkan jenis variabel, ubah generik SeekBar untuk panggilan findViewById ke MySeekBar.

Tinjau contoh kode NowPlayingFragment.kt berikut:

val positionSeekBar: MySeekBar = view.findViewById<MySeekBar>(
     R.id.seekBar
).apply { progress = 0 }
  1. Jalankan aplikasi dan berinteraksi dengan SeekBar. Jika Anda masih mengalami konflik gestur, Anda dapat bereksperimen dan memodifikasi batas thumb di MySeekBar. 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

Dokumen referensi