ViewPager 2 - fungsionalitas baru di bungkus lama

ViewPager adalah salah satu komponen Perpustakaan Dukungan Android yang paling terkenal dan banyak digunakan. Semua komidi putar, papan atas, dan penggeser paling sederhana dibuat di atasnya. Pada bulan Februari 2019, tim pengembangan AndroidX merilis ViewPager2. Mari kita lihat apa saja prasyarat-prasyarat ini dan apa kelebihan dari versi komponen yang diperbarui.



ViewPager 2


Pada saat menulis posting (Juli 2019), versi beta dari ViewPager2 tersedia , yang berarti bahwa masalah yang disebutkan di bawah ini dapat diperbaiki dan fungsionalitas ditingkatkan dan diperluas. Pengembang berjanji di masa depan untuk menambahkan dukungan untuk TabLayout (sementara itu hanya dapat bekerja dengan versi pertama), mengoptimalkan kinerja adaptor, membuat banyak koreksi kecil dan menyelesaikan dokumentasi.

Integrasi


Komponen tidak disertakan dengan paket standar, tetapi terhubung secara terpisah. Untuk melakukan ini, tambahkan baris berikut ke blok dependensi di skrip gradle dari modul Anda:

implementation "androidx.viewpager2:viewpager2:1.0.0-beta02" 

Implementasi


Mari kita mulai dengan kabar baik: beralih dari versi pertama ke versi kedua adalah sesederhana mungkin dan berujung pada perubahan impor. Sintaksis lama yang baik tidak tersentuh: metode getCurrentItem () mengembalikan halaman saat ini, ViewPager2.onPageChangeCallback memungkinkan Anda untuk berlangganan ke keadaan pager, adaptor masih diinstal melalui setAdapter ().



Perlu menggali lebih dalam, karena menjadi jelas bahwa pager pertama dan kedua tidak memiliki kesamaan kecuali antarmuka. Keakraban dengan penerapan metode setAdapter () tidak menyisakan ruang untuk keraguan:

 public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); } 

Ya, ViewPager2 hanyalah pembungkus dari RecyclerView . Di satu sisi, ini merupakan nilai tambah yang besar, di sisi lain - ini menambah sakit kepala. Menyamarkan RecyclerView sebagai selebaran menjadi mungkin dengan munculnya PagerSnapHelper . Kelas ini mengubah fisika gulir. Ketika pengguna melepaskan jarinya, PagerSnapHelper menghitung item daftar mana yang paling dekat dengan garis tengah daftar, dan dengan animasi yang halus selaraskan tepat di tengah. Jadi, jika geseknya cukup tajam, daftar akan bergulir ke elemen berikutnya, jika tidak - dengan animasi kembali ke keadaan semula.

 new PagerSnapHelper().attachToRecyclerView(mRecyclerView); 

gambar
Saat menggunakan PagerSnapHelper, pastikan lebar dan tinggi RecyclerView itu sendiri, serta semua ViewHolders-nya, disetel ke MATCH_PARENT. Jika tidak, perilaku SnapHelper tidak dapat diprediksi, bug dapat terjadi di tempat yang benar-benar tidak terduga. Semua ini membuat korsel elemen dengan ketinggian kecil agak memakan waktu, meskipun mungkin.

Dengan semua hal di atas, dalam tata letak widget akan terlihat seperti ini:

 <androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> 

Dalam paket yang sama dengan ViewPager2, kita juga dapat menemukan kelas ScrollEventAdapter , yang membantu menjaga kontinuitas sintaksis. ScrollEventAdapter mengimplementasikan RecyclerView.OnScrollListener dan mengubah acara gulir menjadi peristiwa OnPageChangeCallback .

 @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... } 

Sekarang OnPageChangeCallback diwakili bukan oleh antarmuka, tetapi oleh kelas abstrak, yang memungkinkan Anda untuk mendefinisikan kembali hanya metode yang diperlukan (dalam kebanyakan kasus, Anda hanya perlu oPageSelected (Int) , yang berfungsi ketika halaman tertentu dipilih):

 main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { //do your stuff } } ) 

Fitur


Yang perlu diperhatikan adalah metode setPageTransformer () , yang menggunakan ViewPager2.PageTransformer sebagai parameter. Ini menetapkan panggilan balik untuk setiap acara pemilihan halaman dan berfungsi untuk mengatur animasi sendiri untuk halaman ini. Callback menerima tampilan halaman saat ini dan nomornya sebagai input. Analog terdekat dengan metode ini adalah ItemAnimator dari RecyclerView .

Dalam versi baru perpustakaan, dua implementasi transformator ditambahkan:

CompositePageTransformer dan MarginPageTransformer . Yang pertama bertanggung jawab untuk menggabungkan transformer untuk menerapkan beberapa transformasi pada satu pager sekaligus, dan yang kedua untuk membuat indentasi antar halaman:

gambar

Selain itu, widget baru mendukung perubahan orientasi: hanya dengan memanggil metode setOrientation () , Anda dapat mengubah pager menjadi daftar vertikal dengan gesek dari atas ke bawah:

 main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL) 

Ini terjadi lagi berkat transisi ke RecyclerView : di bawah tenda, perubahan dalam orientasi LayoutManager disebut , yang bertanggung jawab untuk menampilkan item daftar. Perlu dicatat bahwa mendelegasikan sejumlah besar tugas ke kelas lain telah menguntungkan komponen baru: daftarnya telah menjadi jauh lebih kompak dan mudah dibaca.

Ini bukan akhir dari kesenangan. Dalam satu pembaruan, ViewPager2 menerima dukungan untuk ItemDecoration : kelas pembantu untuk mendekorasi View anak. Mekanisme ini dapat digunakan untuk menggambar pemisah antara elemen, batas, sorotan sel.

Sudah ada banyak implementasi dekorator siap pakai, karena selama bertahun-tahun mereka telah berhasil digunakan ketika bekerja dengan RecyclerView yang biasa. Semua perkembangan sekarang berlaku untuk pager. Di luar kotak, implementasi standar pemisah pager tersedia:

 main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) ) 

Bersamaan dengan pembaruan berikutnya pada Mei 2019, ViewPager2 menambahkan metode penting lainnya: setOffscreenPageLimit (Int) . Dia bertanggung jawab untuk berapa banyak elemen ke kanan dan kiri pusat akan diinisialisasi dalam pager. Meskipun RecyclerView bertanggung jawab untuk menyimpan dan menampilkan Tampilan secara default, menggunakan metode ini Anda dapat secara eksplisit mengatur jumlah item yang ingin dimuat.

Adaptor


Penerus ideologis adaptor pager pertama adalah FragmentStateAdapter : antarmuka interaksi dan penamaan kelas hampir sama. Perubahan hanya memengaruhi penamaan beberapa metode. Jika sebelumnya diperlukan untuk mengimplementasikan fungsi abstrak getItem (posisi) untuk mengembalikan instance Fragment yang diinginkan untuk posisi yang diberikan, dan penamaan ini dapat diinterpretasikan dalam dua cara, sekarang fungsi ini telah diubah namanya menjadi createFragment (posisi) . Jumlah total fragmen disediakan seperti sebelumnya oleh fungsi getCount () .

Dari perubahan struktural penting pada antarmuka, perlu juga dicatat bahwa adaptor sekarang memiliki kemampuan untuk mengontrol siklus hidup unsur-unsurnya, oleh karena itu, bersama dengan FragmentManager dalam konstruktor, ia menerima objek Siklus Hidup , baik Kegiatan atau Fragmen . Karena itu, untuk keamanan, metode saveState () dan restoreState () dinyatakan final dan ditutup untuk warisan.
Kelas FragmentViewHolder bertanggung jawab untuk menyimpan fragmen di dalam RecyclerView . Metode onCreateViewHolder () dari FragmentStateAdapter memanggil FragmentViewHolder.create () .

 static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); } 

Ketika metode onBindViewHolder () dipanggil , pengidentifikasi elemen pada posisi saat ini dan pengidentifikasi ViewHolder terkait , untuk lebih lanjut melampirkan fragmen ke dalamnya:

 final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position); //   

Dan akhirnya, ketika melampirkan wadah dari ViewHolder ke hierarki tampilan, sebuah FragmentTransaction dieksekusi, menambahkan Fragmen ke wadah:

 void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... } 

Dengan demikian, dua penggunaan ViewPager2 muncul : melalui mewarisi kelas adaptor, baik langsung dari RecyclerView.Adapter atau dari FragmentStateAdapter .



Tentunya Anda akan memiliki pertanyaan: mengapa menggunakan pager kedua dengan Fragmen dan adaptor untuk mereka ketika ada versi pertama yang berfungsi normal? ViewPager jauh dari "peluru perak" ketika bekerja dengan daftar data dinamis yang besar. Ini bagus untuk membuat komidi putar dengan set gambar atau spanduk statis, tetapi feed berita dengan paginasi memuat postingan iklan, menyaring melahirkan monster yang didukung keras dan jelek. Cepat atau lambat, Anda pasti akan menemukan hasrat yang membara untuk menulis ulang segala sesuatu di RecyclerView . Sekarang Anda tidak perlu melakukan ini, karena pager itu sendiri mengubahnya, meminjam kemampuannya yang kuat untuk bekerja dengan daftar dinamis, sambil membungkusnya dalam sintaksis yang biasa.

Satu-satunya hal yang dapat ditawarkan PagerAdapter kepada kami adalah metode notifyDataSetChanged () , yang memaksa ViewPager untuk menggambar ulang semua item daftar yang diberikan. Anda mungkin memperhatikan bahwa tidak ada yang menghentikan kami dari menyimpan daftar posisi untuk elemen yang ada dan mengembalikan POSITION_UNCHANGED dari metode getItemPosition () untuk mereka, hanya itu. Namun, solusi ini tidak dapat disebut cantik, itu agak rumit, apalagi, sulit untuk memperluas kasus-kasus ketika elemen-elemen dalam daftar terus berubah, dan tidak hanya ditambahkan secara berurutan sampai akhir. FragmentStateAdapter memiliki gudang penuh metode RecyclerView.Adapter , sehingga logika redrawing elemen dapat dikonfigurasi jauh lebih fleksibel. Selain itu, bersama dengan FragmentStateAdapter, Anda dapat menggunakan DiffUtil , yang memungkinkan Anda untuk secara otomatis mengotomatiskan pekerjaan pemberitahuan perubahan.


Perhatian! Untuk metode notify ... agar berfungsi dengan benar (kecuali untuk notifyDataSetChanged ), metode getItemId (Int) dan c ontainsItem (Long) harus didefinisikan ulang. Ini dilakukan karena implementasi default hanya melihat pada nomor halaman, dan jika, misalnya, Anda menambahkan elemen baru setelah yang sekarang, itu tidak akan ditambahkan, karena getItemId akan tetap tidak berubah. Contoh meng-override dua metode ini berdasarkan daftar elemen bertipe Int :

 override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) } 



Alasan utama kemunculan ViewPager2 adalah keengganan untuk menemukan kembali roda. Di satu sisi, tim pengembangan AndroidX jelas siap untuk meninggalkan ViewPager yang sudah usang sekarang dan tentu saja tidak akan berinvestasi dalam memperluas fungsionalitasnya. Ya, dan mengapa? Lagi pula, RecyclerView sudah mengetahui semua yang diperlukan. Di sisi lain, penghapusan dan penghentian dukungan untuk komponen yang banyak digunakan jelas tidak akan menambah loyalitas masyarakat.

Untuk meringkas: ViewPager2 jelas layak diperhatikan, meskipun saat ini bukan tanpa kelemahan serius.

Cons


  • Kelembaban dan sejumlah besar bug (dapat dimaafkan untuk versi beta);
  • Kedekatan. RecyclerView adalah bidang pribadi dari ViewPager2 , yang menghalangi kita dari banyak peluang: tidak mungkin menerapkan swipe-to-dismiss atau drag-n-drop ( ItemTouchHelper terhubung langsung ke RecyclerView ), Anda tidak dapat mendefinisikan kembali ItemAnimator dengan cara apa pun, tidak mengakses LayoutManager secara langsung dan menggunakan RecycledViewPool secara langsung. Namun, dengan rilis versi baru komponen, jumlah metode antarmuka yang diwarisi dari RecyclerView semakin bertambah (misalnya, ItemDecoration ), dan kami berharap dapat menambahkan metode yang hilang di masa mendatang.

Pro


  • Mendukung semua keuntungan RecyclerView.Adapter : menggabungkan elemen dari tipe yang berbeda dalam satu daftar, menambah dan menghapus elemen secara langsung selama swipe, animasi menggambar ulang isi daftar ketika mengubah;
  • Dukungan untuk spektrum penuh metode notify ... dan perhitungan otomatis perubahan menggunakan DiffUtil ;
  • Kemudahan transisi karena kesinambungan sintaksis;
  • Dukungan untuk orientasi vertikal dan horizontal "out of the box";
  • Dukungan RTL ;
  • Mendukung ItemDecorator ;
  • Dukungan untuk menggulir perangkat lunak melalui fakeScrollBy () ;
  • Kemampuan untuk secara manual mengatur jumlah item yang dimuat;
  • Kemampuan untuk menggunakan salah satu solusi open-source yang siap pakai untuk mengurangi kode boilerplate , yang tidak dapat dihindari ketika menulis RecyclerView.Adapter kustom. Misalnya, EasyAdapter .

Sebagai ringkasan, saya ingin mengatakan bahwa ViewPager2 benar-benar layak untuk dilihat lebih dekat . Ini adalah solusi yang menjanjikan, dapat dikembangkan, dan fungsional. Dan meskipun masih terlalu dini untuk meluncurkan widget baru dalam produksi, aman untuk mengatakan bahwa setelah rilis penuh ia dapat dan harus sepenuhnya menggantikan leluhurnya.

Bagi mereka yang berani dan tegas, yang terinspirasi artikel untuk bereksperimen, PagerSnapHelper muncul di versi 28 dari Perpustakaan Dukungan , yang berarti Anda dapat menggunakannya bersama dengan RecyclerView Anda dengan membuat ViewPager2 sendiri .

Contoh operasi dari ViewPager2 dan FragmentStateAdapter .

Rilis-catatan resmi ViewPager2

Source: https://habr.com/ru/post/id461873/


All Articles