Daftar adalah inti dari sebagian besar aplikasi Android. Selama bertahun-tahun, berbagai solusi diperkenalkan guna memastikan komponen UI lainnya dapat berinteraksi dengan daftar tersebut — misalnya, reaksi panel aplikasi terhadap scroll daftar atau interaksi daftar bertumpuk dengan satu sama lain. Pernahkah Anda menghadapi situasi saat Anda memiliki satu daftar di dalam daftar lainnya dan, dengan men-scroll daftar bagian dalam sampai akhir, Anda ingin daftar bagian luar melanjutkan pergerakannya? Itulah contoh scroll bertingkat klasik!
Scroll bertingkat adalah sistem dengan komponen scroll yang ada di dalam satu sama lain yang dapat mengomunikasikan delta scroll-nya agar dapat bekerja sama. Sebagai contoh, dalam sistem View, NestedScrollingParent
dan NestedScrollingChild
adalah komponen penyusun untuk scroll bertingkat. Konstruksi ini digunakan oleh komponen seperti NestedScrollView
dan RecyclerView
untuk memungkinkan banyak kasus penggunaan scroll bertingkat. Scroll bertingkat merupakan fitur utama di banyak framework UI, dan dalam postingan blog ini, kita akan melihat bagaimana Jetpack Compose menanganinya.
Mari kita lihat kasus penggunaan ketika sistem scroll bertingkat dapat bermanfaat. Dalam contoh ini, kita akan membuat efek panel aplikasi yang dapat diciutkan khusus di aplikasi kita. Panel aplikasi yang dapat diciutkan akan berinteraksi dengan daftar untuk menciptakan efek menyembunyikan — kapan pun, jika panel aplikasi diperluas, men-scroll daftar ke atas akan membuatnya disembunyikan. Demikian pula, jika panel aplikasi diciutkan, men-scroll daftar ke bawah akan membuatnya meluas. Berikut ini contoh tampilannya:
Anggaplah aplikasi kita terdiri dari panel aplikasi dan daftar, yang berlaku untuk banyak aplikasi.
Catatan: Anda dapat mencapai perilaku serupa dengan menggunakan parameter TopAppBar scrollBehavior
Material 3, namun kami menulis ulang beberapa logika tersebut untuk mengilustrasikan cara kerja sistem scroll bertingkat.
Kode ini merender hal berikut:
Secara default, tidak ada komunikasi antara panel aplikasi dan daftar. Jika kita men-scroll daftarnya, panel aplikasi bersifat statis. Salah satu alternatifnya adalah menjadikan panel aplikasi menjadi bagian dari daftar itu sendiri, namun kita segera melihat bahwa cara itu tidak akan berhasil. Setelah men-scroll daftar ke bawah, kita perlu men-scroll lagi ke atas untuk melihat panel aplikasi:
Dengan memeriksa masalah ini, kami melihat bahwa kami ingin mempertahankan tempat hierarki panel aplikasi (di luar daftar). Namun, kami juga ingin bereaksi terhadap perubahan scroll dalam daftar — yaitu, membuat komponen bereaksi terhadap scroll daftar. Ini adalah petunjuk bahwa sistem scroll bertingkat di Compose mungkin solusi yang bagus untuk masalah ini.
Sistem scroll bertingkat adalah solusi yang bagus jika Anda ingin koordinasi antar komponen ketika satu atau beberapa komponen dapat di-scroll dan tertaut secara hierarki (dalam kasus di atas, panel aplikasi dan daftar memiliki induk yang sama). Sistem ini menghubungkan kontainer scroll dan memberikan kesempatan bagi kita untuk berinteraksi dengan delta scroll yang sedang disebarkan/dibagikan di antara mereka.
Mempresentasikan: Siklus scroll bertingkat
Mari kita kembali sedikit dan membahas cara kerja scroll bertingkat secara umum. Siklus scroll bertingkat adalah aliran delta scroll (perubahan) yang dikirim ke atas dan ke bawah pohon hierarki melalui seluruh komponen yang dapat menjadi bagian dari sistem scroll bertingkat.
Mari kita ambil sebuah daftar sebagai contoh. Saat peristiwa gestur terdeteksi, bahkan sebelum daftar itu sendiri dapat di-scroll, delta akan dikirim ke sistem scroll bertingkat. Delta yang dihasilkan oleh peristiwa tersebut akan melalui 3 tahap: pra-scroll, pemakaian node, dan pasca-scroll.
- Pada tahap pra-scroll, komponen yang menerima delta sentuh akan mengirimkan peristiwa tersebut melalui pohon hierarki ke induk paling atas. Kemudian peristiwa delta akan melakukan bubble down, artinya delta akan disebarkan dari induk paling dasar ke bawah menuju turunan yang memulai siklus scroll bertingkat. Hal ini memberikan peluang bagi induk scroll bertingkat di sepanjang jalur ini (composable yang menggunakan pengubah
nestedScroll
) untuk “melakukan sesuatu” dengan delta sebelum node itu sendiri dapat menggunakannya.
Jika kita kembali ke diagram, turunan (daftar, misalnya) yang men-scroll 10 piksel akan memulai proses scroll bertingkat. Turunan tersebut akan mengirimkan 10 piksel ke atas rantai ke induk paling dasar yang selama tahap pra-scroll, induk akan diberi kesempatan untuk menggunakan 10 piksel tersebut:
Dalam perjalanan menuju turunan yang memulai proses, setiap induk dapat memilih untuk menggunakan sebagian dari 10 piksel dan sisanya akan disebarkan ke bawah rantai. Ketika sudah sampai di turunan, kita akan masuk ke tahap pemakaian node. Dalam contoh ini, induk 1 memilih untuk menggunakan 5 piksel, sehingga tersisa 5 piksel untuk tahap berikutnya.
- Pada tahap pemakaian node, node itu sendiri akan menggunakan delta apa pun yang tidak digunakan oleh induknya. Ini adalah momen ketika, misalnya, sebuah daftar akan benar-benar bergerak.
Selama tahap ini, turunan dapat memilih untuk menggunakan sebagian scroll yang tersisa atau seluruhnya. Sisanya akan dikirim kembali untuk melewati tahap pasca-scroll. Turunan dalam diagram kita hanya menggunakan 2 piksel untuk bergerak, menyisakan 3 piksel untuk tahap berikutnya.
- Terakhir, pada tahap pasca-scroll, apa pun yang tidak digunakan oleh node itu sendiri akan dikirim naik lagi ke pendahulunya jika ada orang yang ingin menggunakannya.
Tahap pasca-scroll akan bekerja dengan cara yang sama seperti tahap pra-scroll, ketika setiap induk dapat memilih untuk menggunakannya.
Selama tahap ini, induk 2 menggunakan 3 piksel yang tersisa dan melaporkan 0 piksel sisanya ke bawah rantai.
Demikian pula, saat gestur menyeret selesai, tujuan pengguna dapat diterjemahkan menjadi kecepatan yang akan digunakan untuk “menggesek cepat” daftar — yaitu, membuatnya di-scroll menggunakan animasi. Gesek cepat juga merupakan bagian dari siklus scroll bertingkat, dan kecepatan yang dihasilkan oleh peristiwa tarik akan melalui tahap serupa: sebelum gesek cepat, pemakaian node, dan setelah gesek cepat.
Oke, tapi apa relevansinya dengan masalah awal kita? Compose menyediakan seperangkat fitur yang dapat kita gunakan untuk memengaruhi cara kerja tahap-tahap ini dan berinteraksi langsung dengannya. Dalam kasus kami, jika panel aplikasi sedang ditampilkan dan kami men-scroll daftar ke atas, kami ingin memprioritaskan scroll panel aplikasi. Di sisi lain, jika kita men-scroll ke bawah dan panel aplikasi tidak muncul, kami juga ingin memprioritaskan scroll panel aplikasi sebelum men-scroll daftar itu sendiri. Ini adalah petunjuk lain bahwa sistem scroll bertingkat mungkin solusi yang bagus: kasus penggunaan kita membuat kita ingin melakukan sesuatu dengan delta scroll bahkan sebelum daftar di-scroll (lihat tautan dengan tahap pra-scroll di atas).
Selanjutnya mari kita lihat fitur-fitur ini.
Pengubah scroll bertingkat
Jika kita menganggap siklus scroll bertingkat sebagai sistem yang bekerja pada rantai node, pengubah scroll bertingkat adalah cara kita memasukkan diri kita ke dalam perubahan dan memengaruhi data (delta scroll) yang disebarkan dalam rantai ini. Pengubah ini dapat ditempatkan di mana saja dalam hierarki, dan berkomunikasi dengan instance pengubah scroll bertingkat di atas pohon sehingga dapat berbagi informasi melalui saluran ini. Untuk berinteraksi dengan informasi yang diteruskan melalui saluran ini, Anda dapat menggunakan NestedScrollConnection
yang akan memanggil callback tertentu, tergantung tahap pemakaian. Mari kita lihat lebih dalam komponen penyusunan pengubah ini:
NestedScrollConnection
: Koneksi adalah cara untuk merespons tahap siklus scroll bertingkat. Ini adalah cara utama Anda dapat memengaruhi sistem scroll bertingkat. Ini terdiri dari 4 metode callback, masing-masing mewakili salah satu tahap: pra/pasca-scroll dan sebelum/setelah gesek cepat. Setiap callback juga memberikan informasi mengenai delta yang disebarkan:
1. tersedia
: Delta yang tersedia untuk tahap tertentu.
2.digunakan
: Delta digunakan pada tahap sebelumnya. Misalnya, onPostScroll
memiliki argumen “dipakai” , yang mengacu pada berapa banyak yang dipakai selama tahap pemakaian node. Kita dapat menggunakan nilai ini untuk mengetahui, misalnya, berapa banyak daftar asal yang sudah di-scroll, karena nilai ini akan dipanggil setelah tahap pemakaian node.
3. sumber scroll bertingkat: Tempat asal delta tersebut — Drag
(jika berasal dari gestur), atau Fling
(jika berasal dari animasi gesek cepat).
Nilai yang ditampilkan dalam callback adalah cara kita memberi tahu cara berperilaku kepada sistem. Kita akan membahasnya lebih lanjut sebentar lagi.
NestedScrollDispatcher
: Operator adalah entitas yang memulai siklus scroll bertingkat — yaitu, menggunakan operator dan memanggil metodenya pada dasarnya akan memicu siklus tersebut. Misalnya, kontainer yang dapat di-scroll memiliki operator bawaan yang menangani pengiriman delta yang ditangkap selama gestur ke dalam sistem. Karena alasan ini, sebagian besar kasus penggunaan akan melibatkan penggunaan koneksi dan bukan operator karena kita bereaksi terhadap delta yang sudah ada, dan tidak mengirim yang baru.
Sekarang, mari pikirkan tentang hal yang kita ketahui mengenai urutan propagasi delta dalam sistem scroll bertingkat dan coba terapkan informasi tersebut ke kasus penggunaan kita untuk melihat bagaimana kita dapat menerapkan perilaku penciutan panel aplikasi yang benar. Sebelumnya kita telah mempelajari bahwa, setelah peristiwa scroll dipicu, bahkan sebelum daftar itu sendiri dapat bergerak, kita akan diberi kesempatan untuk membuat keputusan tentang posisi panel aplikasi. Ini mengisyaratkan bahwa kita perlu melakukan sesuatu selama onPreScroll
. Ingat, onPreScroll
adalah tahap yang terjadi tepat sebelum daftar di-scroll (tahap NodeConsumption
).
Kode awal kita adalah kombinasi dua composable, satu untuk panel aplikasi dan satu lagi untuk daftar yang diapit dengan Box
:
Tinggi panel aplikasi sudah tetap dan kita cukup mengimbangi posisinya untuk menampilkan/menyembunyikannya. Mari kita buat variabel status untuk menampung nilai offset tersebut:
Sekarang, kita perlu memperbarui offset berdasarkan scroll daftar. Kita akan menginstal koneksi scroll bertingkat pada posisi dalam hierarki yang dapat menangkap delta yang berasal dari daftar; pada saat yang bersamaan, ia harus dapat mengubah offset panel aplikasi. Tempat yang bagus adalah induk yang sama dari keduanya — induk memiliki posisi yang bagus secara hierarki untuk 1) menerima delta dari satu komponen dan 2) memengaruhi posisi komponen lainnya. Kita akan menggunakan koneksi tersebut untuk memengaruhi tahap onPreScroll
:
Pada callback onPreScroll
kita akan menerima delta dari daftar di parameter tersedia
. Tampilan callback ini harus berupa tampilan apa pun yang kita gunakan dari yang tersedia. Artinya jika kita menampilkan Offset.Zero
, kita tidak menggunakan apa pun dan daftar akan dapat menggunakan semuanya untuk scroll. Jika kita menampilkan available
, daftar tidak akan ada yang tersisa, sehingga tidak akan di-scroll.
Untuk kasus penggunaan kita, jika appBarOffset
kita bernilai antara 0 dan tinggi maksimal panel aplikasi, kita perlu memberikan delta ke panel aplikasi (tambahkan ke offset). Kita dapat mencapainya dengan penghitungan menggunakan coerceIn
(ini membatasi nilai antara minimum dan maksimum). Setelah itu, kita perlu melaporkan kembali ke sistem tentang hal yang digunakan oleh offset panel aplikasi. Pada akhirnya, implementasi onPreScroll
terlihat seperti ini:
Mari kita atur ulang kode kita sedikit dan abstrakkan offset status dan koneksinya menjadi satu kelas:
Dan sekarang, kita bisa menggunakan kelas tersebut untuk menyeimbangkan appBar
kita:
Sekarang, daftar akan tetap bersifat statis sampai panel aplikasi diciutkan sepenuhnya karena offset panel aplikasi memakai seluruh delta dan tidak ada lagi yang tersisa untuk digunakan dalam daftar.
Ini bukanlah hal yang kita inginkan. Untuk memperbaikinya, kita perlu menggunakan appBarOffset
untuk turut memperbarui area spasi sebelum daftar kita sehingga saat panel aplikasi diciutkan sepenuhnya, tinggi item akan disetel ulang. Setelah itu, panel aplikasi tidak akan menggunakan apa pun lagi, sehingga daftar dapat di-scroll dengan bebas.
Logika ini juga berlaku untuk memperluas panel aplikasi. Saat panel aplikasi diluaskan, daftarnya bersifat statis, namun item yang tidak terlihat bertambah sehingga ini memberikan ilusi bahwa daftar bergerak. Setelah diperluas sepenuhnya, panel aplikasi tidak akan menggunakan delta lagi, dan daftar akan dapat terus di-scroll.
Pada hasil akhirnya, panel aplikasi akan diciutkan/diperluas sebelum daftar di-scroll seperti yang diharapkan.
Kesimpulannya:
- Kita dapat menggunakan sistem scroll bertingkat sebagai cara yang memungkinkan interaksi komponen di tempat berbeda dalam hierarki Compose dengan komponen scroll.
- Kita dapat menggunakan
NestedScrollConnection
untuk mengizinkan perubahan pada delta yang disebarkan dalam siklus scroll bertingkat. - Kita harus mengganti metode
onPreScroll
/onPostScroll
untuk mengubah delta scroll dan onPreFling
/onPostFling
untuk mengubah kecepatan gesek cepat. - Selalu ingat untuk menampilkan apa pun yang dipakai di setiap metode yang diganti sehingga siklus scroll bertingkat dapat melanjutkan propagasi.
Jika Anda ingin mempelajari selengkapnya tentang sistem scroll, lihat dokumentasi resmi yang berisi diskusi yang lebih teknis tentang API yang digunakan di sini dan cara Anda dapat melakukan interop dengan sistem scroll bertingkat View.
Lisensi cuplikan kode: Hak Cipta 2024 Google LLC.
Identifier Lisensi SPDX: Apache-2.0