Membuat navigasi responsif

Navigasi adalah interaksi pengguna dengan UI aplikasi untuk mengakses tujuan konten. Prinsip navigasi Android memberikan panduan yang membantu Anda membuat navigasi aplikasi yang konsisten dan intuitif.

UI responsif/adaptif menyediakan tujuan konten responsif dan sering kali menyertakan berbagai jenis elemen navigasi sebagai respons terhadap perubahan ukuran layar—misalnya, menu navigasi bawah pada layar kecil, kolom samping navigasi pada layar berukuran sedang, atau panel navigasi persisten pada layar besar—tetapi UI responsif/adaptif harus tetap sesuai dengan prinsip navigasi.

Komponen Navigasi Jetpack menerapkan prinsip navigasi dan memfasilitasi pengembangan aplikasi dengan UI responsif/adaptif.

Gambar 1. Tampilan luas, sedang, dan ringkas dengan panel navigasi, kolom samping, dan panel bawah.

Navigasi UI responsif

Ukuran jendela tampilan yang ditempati oleh aplikasi memengaruhi ergonomi dan kegunaan. Class ukuran jendela memungkinkan Anda menentukan elemen navigasi yang sesuai (seperti menu navigasi, kolom samping, atau panel samping) dan menempatkannya di tempat yang paling mudah diakses oleh pengguna. Dalam pedoman tata letak Desain Material, elemen navigasi menempati ruang tetap di tepi depan layar dan dapat dipindahkan ke tepi bawah jika lebar aplikasinya rapat. Pilihan elemen navigasi sangat bergantung pada ukuran jendela aplikasi dan jumlah item yang harus disimpan elemen.

Class ukuran jendela Sedikit item Banyak item
lebar rapat menu navigasi bawah panel navigasi (tepi depan atau bawah)
lebar sedang kolom samping navigasi panel navigasi (tepi depan)
lebar diperluas kolom samping navigasi panel navigasi persisten (tepi depan)

File resource tata letak dapat dikualifikasikan oleh titik henti sementara class ukuran jendela untuk menggunakan elemen navigasi yang berbeda untuk dimensi tampilan yang berbeda.

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

<!-- res/layout-w600dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigationrail.NavigationRailView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

<!-- res/layout-w1240dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

Tujuan konten responsif

Pada UI yang responsif, tata letak setiap tujuan konten akan menyesuaikan dengan perubahan ukuran jendela. Aplikasi Anda dapat menyesuaikan spasi tata letak, memosisikan ulang elemen, menambahkan atau menghapus konten, atau mengubah elemen UI, termasuk elemen navigasi.

Jika setiap tujuan dapat menangani peristiwa perubahan ukuran, perubahan akan diisolasi ke UI. Status aplikasi lainnya, seperti navigasi, tidak akan terpengaruh.

Navigasi tidak boleh terjadi sebagai efek samping dari perubahan ukuran jendela. Jangan membuat tujuan konten hanya untuk mengakomodasi ukuran jendela yang berbeda. Misalnya, jangan membuat tujuan konten yang berbeda untuk layar perangkat foldable yang berbeda.

Menavigasi ke tujuan konten sebagai efek samping dari perubahan ukuran jendela memiliki masalah berikut:

  • Tujuan lama (untuk ukuran jendela sebelumnya) mungkin akan terlihat sesaat sebelum menavigasi ke tujuan baru
  • Agar tetap dapat dikembalikan ke setelan semula (misalnya, saat perangkat dilipat dan dibentangkan), navigasi diperlukan untuk setiap ukuran jendela
  • Mempertahankan status aplikasi antartujuan bisa jadi tidak mudah karena navigasi dapat merusak status setelah memunculkan data sebelumnya

Selain itu, aplikasi Anda mungkin tidak berada di latar depan saat perubahan ukuran jendela terjadi. Tata letak aplikasi Anda mungkin memerlukan lebih banyak ruang daripada aplikasi latar depan, dan saat pengguna kembali ke aplikasi Anda, orientasi dan ukuran jendela semuanya mungkin telah berubah.

Jika aplikasi Anda memerlukan tujuan konten unik berdasarkan ukuran jendela, sebaiknya gabungkan tujuan yang relevan ke dalam satu tujuan yang menyertakan tata letak alternatif dan adaptif.

Tujuan konten dengan tata letak alternatif

Sebagai bagian dari desain responsif/adaptif, satu tujuan navigasi dapat memiliki tata letak alternatif bergantung pada ukuran jendela aplikasi. Setiap tata letak menggunakan seluruh jendela, tetapi tata letak yang berbeda disajikan untuk ukuran jendela yang berbeda (desain adaptif).

Contoh kanonis adalah tampilan detail daftar. Untuk ukuran jendela rapat, aplikasi Anda akan menampilkan satu tata letak konten untuk daftar dan satu tata letak untuk detailnya. Menavigasi ke tujuan tampilan daftar-detail awalnya hanya akan menampilkan tata letak daftar. Saat item daftar dipilih, aplikasi Anda akan menampilkan tata letak detail, menggantikan daftar. Saat kontrol kembali dipilih, tata letak daftar akan ditampilkan, menggantikan detail. Namun, untuk ukuran jendela yang diperluas, tata letak daftar dan detail akan ditampilkan berdampingan.

SlidingPaneLayout memungkinkan Anda membuat satu tujuan navigasi yang menampilkan dua panel konten secara berdampingan di layar besar, tetapi hanya satu panel sekaligus di layar kecil seperti di ponsel konvensional.

<!-- Single destination for list and detail. -->

<navigation ...>

    <!-- Fragment that implements SlidingPaneLayout. -->
    <fragment
        android:id="@+id/article_two_pane"
        android:name="com.example.app.ListDetailTwoPaneFragment" />

    <!-- Other destinations... -->
</navigation>

Lihat Membuat tata letak dua panel untuk mengetahui detail cara menerapkan tata letak daftar-detail menggunakan SlidingPaneLayout.

Satu grafik navigasi

Untuk memberikan pengalaman pengguna yang konsisten di semua ukuran jendela atau perangkat, gunakan satu grafik navigasi dengan tata letak setiap tujuan konten yang responsif.

Jika Anda menggunakan grafik navigasi yang berbeda untuk setiap class ukuran jendela, setiap kali aplikasi bertransisi dari satu class ukuran ke class ukuran lainnya, Anda harus menentukan tujuan pengguna saat ini di grafik lain, membuat data sebelumnya, dan merekonsiliasi informasi status yang berbeda di antara grafik.

Host navigasi bertingkat

Aplikasi Anda mungkin menyertakan tujuan konten yang memiliki tujuan kontennya sendiri. Misalnya, dalam tata letak daftar-detail, panel detail item dapat menyertakan elemen UI yang menavigasi ke konten yang menggantikan detail item.

Untuk menerapkan jenis subnavigasi ini, buat panel detail menjadi host navigasi bertingkat dengan grafik navigasinya sendiri yang menentukan tujuan yang diakses dari panel detail:

<!-- layout/two_pane_fragment.xml -->

<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_pane"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"/>

    <!-- Detail pane is a nested navigation host. Its graph is not connected
         to the main graph that contains the two_pane_fragment destination. -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/detail_pane"
        android:layout_width="300dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>

Ini berbeda dengan grafik navigasi bertingkat karena grafik navigasi NavHost bertingkat tidak terhubung ke grafik navigasi utama; artinya, Anda tidak dapat menavigasi langsung dari tujuan dalam satu grafik ke tujuan di grafik lainnya.

Untuk informasi selengkapnya, lihat Grafik navigasi bertingkat.

Status yang dipertahankan

Untuk memberikan tujuan konten yang responsif, aplikasi harus mempertahankan statusnya saat perangkat diputar atau dilipat, atau jendela aplikasi diubah ukurannya. Secara default, perubahan konfigurasi seperti ini akan membuat ulang aktivitas, fragmen, dan hierarki tampilan aplikasi. Cara yang direkomendasikan untuk menyimpan status UI adalah dengan ViewModel, yang tetap ada meskipun terjadi perubahan konfigurasi. (Lihat Menyimpan status UI .)

Perubahan ukuran harus dapat dikembalikan ke setelan semula, misalnya, saat pengguna memutar perangkat lalu memutarnya kembali.

Tata letak responsif/adaptif dapat menampilkan konten yang berbeda dengan ukuran jendela yang berbeda. Dengan demikian, tata letak responsif sering kali perlu menyimpan status tambahan yang berkaitan dengan konten, meskipun status tersebut tidak berlaku untuk ukuran jendela saat ini. Misalnya, tata letak mungkin memiliki ruang untuk menampilkan widget scroll tambahan hanya pada lebar jendela yang lebih besar. Jika peristiwa pengubahan ukuran menyebabkan lebar jendela menjadi terlalu kecil, widget akan disembunyikan. Saat ukuran aplikasi berubah menjadi dimensi sebelumnya, widget scroll akan terlihat lagi, dan posisi scroll asli seharusnya dipulihkan.

Cakupan ViewModel

Panduan developer Bermigrasi ke komponen Navigasi menetapkan arsitektur aktivitas tunggal tempat tujuan diterapkan sebagai fragmen dan model datanya diterapkan menggunakan ViewModel.

ViewModel selalu dicakup ke siklus proses, dan saat siklus proses tersebut berakhir secara permanen, ViewModel akan dihapus dan dapat dibuang. Siklus proses yang mencakup ViewModel—dan dengan demikian, seberapa luas ViewModel dapat dibagikan—bergantung pada delegasi properti yang digunakan untuk mendapatkan ViewModel.

Dalam kasus yang paling sederhana, setiap tujuan navigasi adalah satu fragmen dengan status UI yang sepenuhnya terisolasi; sehingga setiap fragmen dapat menggunakan delegasi properti viewModels() untuk mendapatkan ViewModel yang dicakupkan ke fragmen tersebut.

Untuk berbagi status UI antarfragmen, buat cakupan ViewModel ke aktivitas dengan memanggil activityViewModels() dalam fragmen (yang setara dengan Activity hanya viewModels()). Hal ini memungkinkan aktivitas dan fragmen apa pun yang terpasang di aktivitas untuk berbagi instance ViewModel. Namun, dalam arsitektur aktivitas tunggal, cakupan ViewModel ini berlaku secara efektif selama aplikasi berjalan, sehingga ViewModel tetap ada di memori meskipun tidak ada fragmen yang menggunakannya.

Misalnya grafik navigasi Anda memiliki urutan tujuan fragmen yang mewakili alur checkout, dan status saat ini untuk seluruh pengalaman checkout berada dalam ViewModel yang dibagikan di antara fragmen. Penentuan cakupan ViewModel ke aktivitas tidak hanya terlalu luas, tetapi sebenarnya juga mengekspos masalah lain: jika pengguna melalui alur checkout untuk satu pesanan, lalu melaluinya lagi untuk pesanan kedua, kedua pesanan tersebut menggunakan instance ViewModel checkout yang sama. Sebelum checkout pesanan kedua, Anda harus menghapus data dari pesanan pertama secara manual. Setiap kesalahan dapat merugikan pengguna.

Sebagai gantinya, buat cakupan ViewModel ke grafik navigasi di NavController saat ini. Buat grafik navigasi bertingkat untuk mengenkapsulasi tujuan yang merupakan bagian dari alur checkout. Kemudian, di setiap tujuan fragmen tersebut, gunakan delegasi properti navGraphViewModels(), dan teruskan ID grafik navigasi untuk mendapatkan ViewModel bersama. Hal ini memastikan bahwa setelah pengguna keluar dari alur checkout dan grafik navigasi bertingkat berada di luar cakupan, instance ViewModel yang terkait akan dihapus dan tidak akan digunakan untuk checkout berikutnya.

Cakupan Delegasi properti Dapat berbagi ViewModel dengan
Fragmen Fragment.viewModels() Hanya fragmen
Aktivitas Activity.viewModels() atau Fragment.activityViewModels() Aktivitas dan semua fragmen yang disematkan
Grafik navigasi Fragment.navGraphViewModels() Semua fragmen di grafik navigasi yang sama

Perhatikan bahwa jika Anda menggunakan host navigasi bertingkat (lihat bagian Host navigasi bertingkat), tujuan dalam host tersebut tidak dapat berbagi instance ViewModel dengan tujuan di luar host saat menggunakan navGraphViewModels() karena grafik tidak terhubung. Dalam hal ini, Anda dapat menggunakan cakupan aktivitas.

Referensi lainnya