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