Memahami RecyclerView setHasFixedSize

141

Saya kesulitan memahaminya setHasFixedSize(). Saya tahu bahwa ini digunakan untuk pengoptimalan ketika ukuran RecyclerViewtidak berubah, dari dokumen.

Apa artinya itu? Dalam kasus yang paling umum, ListViewhampir selalu memiliki ukuran tetap. Dalam kasus apa itu bukan ukuran tetap? Apakah ini berarti real estat aktual yang ditempati di layar tumbuh bersama konten?

SIr Codealot
sumber
Saya menemukan jawaban ini membantu dan sangat mudah dimengerti [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan

Jawaban:

117

Sebuah sangat versi sederhana dari RecyclerView memiliki:

void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();
}

Tautan ini menjelaskan mengapa biaya menelepon requestLayoutmungkin mahal. Pada dasarnya setiap kali item disisipkan, dipindahkan atau dihapus, ukuran (lebar dan tinggi) RecyclerView mungkin berubah dan pada gilirannya ukuran tampilan lain dalam hierarki tampilan mungkin berubah. Ini sangat merepotkan jika item ditambahkan atau dihapus secara sering.

Hindari tata letak yang tidak perlu lewat dengan menyetel setHasFixedSizeke true saat mengubah konten adaptor tidak mengubah tinggi atau lebarnya.


Pembaruan: JavaDoc telah diperbarui untuk lebih menggambarkan apa yang sebenarnya dilakukan metode ini.

RecyclerView bisa melakukan beberapa pengoptimalan jika ia bisa mengetahui sebelumnya bahwa ukuran RecyclerView tidak dipengaruhi oleh konten adaptor. RecyclerView masih bisa mengubah ukurannya berdasarkan faktor lain (misalnya ukuran induknya) tetapi penghitungan ukuran ini tidak dapat bergantung pada ukuran turunannya atau konten adaptornya (kecuali jumlah item di adaptor).

Jika penggunaan RecyclerView Anda termasuk dalam kategori ini, setel ke {@code true}. Ini akan memungkinkan RecyclerView menghindari membuat seluruh tata letak tidak valid saat konten adaptornya berubah.

@param hasFixedSize true jika perubahan adaptor tidak dapat memengaruhi ukuran RecyclerView.

LukaCiko
sumber
173
Ukuran RecyclerView berubah setiap kali Anda menambahkan sesuatu apa pun yang terjadi. Apa yang dilakukan setHasFixedSize adalah memastikan (dengan input pengguna) bahwa perubahan ukuran RecyclerView ini konstan. Tinggi (atau lebar) item tidak akan berubah. Setiap item yang ditambahkan atau dihapus akan sama. Jika Anda tidak mengatur ini, itu akan memeriksa apakah ukuran item telah berubah dan itu mahal. Klarifikasi saja karena jawaban ini membingungkan.
Arnold Balliu
10
@ArnoldB klarifikasi yang sangat baik. Saya bahkan akan membantahnya sebagai jawaban yang berdiri sendiri.
young_souvlaki
4
@ArnoldB - Saya masih bingung. Apakah Anda menyarankan bahwa kita harus menyetel hasFixedSize menjadi true jika lebar / tinggi semua turunan konstan? Jika ya, bagaimana jika ada kemungkinan bahwa beberapa turunan dapat dihapus saat runtime (saya memiliki fitur geser untuk menutup) - apakah boleh menyetel true?
Jaguar
1
Iya. Karena lebar dan tinggi barang tidak berubah. Itu hanya ditambahkan atau dihapus. Menambahkan atau menghapus item tidak mengubah ukurannya.
Arnold Balliu
3
@ArnoldB Saya tidak berpikir ukuran (lebar / tinggi) item menjadi masalah di sini. Itu juga tidak akan memeriksa ukuran item. Itu hanya memberitahu RecyclerView untuk memanggil requestLayoutatau tidak setelah dataSet diperbarui.
Kimi Chiu
25

Dapat mengonfirmasi setHasFixedSizeterkait dengan RecyclerView itu sendiri, dan bukan ukuran setiap item yang disesuaikan dengannya.

Sekarang Anda dapat menggunakan android:layout_height="wrap_content"di RecyclerView, yang, antara lain, memungkinkan CollapsingToolbarLayout mengetahui bahwa ia tidak boleh diciutkan saat RecyclerView kosong. Ini hanya berfungsi saat Anda menggunakan setHasFixedSize(false)di RecylcerView.

Jika Anda menggunakan setHasFixedSize(true)RecyclerView, perilaku untuk mencegah CollapsingToolbarLayout agar tidak runtuh tidak akan berfungsi, meskipun RecyclerView memang kosong.

Jika setHasFixedSizedikaitkan dengan ukuran item, seharusnya tidak berpengaruh apa pun saat RecyclerView tidak memiliki item.

Kevin
sumber
4
Saya baru saja mengalami pengalaman yang menunjuk ke arah yang sama. Menggunakan RecyclerView dengan GridLayoutManager (3 item per baris) dan layout_height = wrap_content. Saat saya mengklik tombol yang menambahkan 3 item baru ke daftar, tampilan pendaur ulang tidak meluas agar sesuai dengan item baru. Sebaliknya, ia mempertahankan ukuran yang sama, dan satu-satunya cara untuk melihat item baru adalah menggulirnya. Meskipun item-itemnya memiliki ukuran yang sama, saya harus menghapusnya setHasFixedSize(true)untuk membuatnya mengembang ketika item baru ditambahkan.
Mateus Gondim
Saya pikir Anda benar. Dari dokumen, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.Jadi meskipun ukuran item akan berubah, Anda masih dapat menyetelnya ke true.
Kimi Chiu
15

ListView memiliki fungsi bernama serupa yang menurut saya mencerminkan info tentang ukuran ketinggian item daftar individual. Dokumentasi untuk RecyclerView dengan cukup jelas menyatakan bahwa hal itu mengacu pada ukuran RecyclerView itu sendiri, bukan ukuran itemnya.

Dari komentar sumber RecyclerView di atas metode setHasFixedSize ():

 * RecyclerView can perform several optimizations if it can know in advance that changes in
 * adapter content cannot change the size of the RecyclerView itself.
 * If your use of RecyclerView falls into this category, set this to true.
dangVarmit
sumber
16
Tapi bagaimana "ukuran" RecyclerView didefinisikan? Apakah ukurannya hanya terlihat di layar, atau ukuran penuh RecyclerView, yang sama dengan (jumlah tinggi item + padding + spasi)?
Vicky Chijwani
2
Memang ini butuh info lebih lanjut. Jika Anda menghapus item dan recyclerview menyusut, apakah itu dianggap telah berubah ukuran?
Henrique de Sousa
4
Saya akan menganggapnya seperti bagaimana TextView bisa menata dirinya sendiri. Jika Anda menentukan wrap_content, maka saat Anda menyetel teks, TextView dapat meminta izin tata letak dan mengubah jumlah ruang yang ditempati di layar. Jika Anda menentukan match_parent atau dimensi tetap, TextView tidak akan meminta pass tata letak karena ukurannya tetap dan jumlah teks di dalam tidak akan pernah mengubah jumlah ruang yang ditempati. RecyclerView sama. setHasFixedSize () mengisyaratkan RV yang seharusnya tidak perlu meminta penerusan tata letak berdasarkan perubahan pada item adaptor.
dangVarmit
1
@dangVarmit penjelasan yang bagus!
howerknea
8

Jika kita memiliki RecyclerViewdengan match_parentsebagai tinggi / lebar , kita harus menambahkan setHasFixedSize(true)karena ukuran RecyclerViewitu sendiri tidak mengubah memasukkan atau menghapus item ke dalamnya.

setHasFixedSize harus palsu jika kita memiliki RecyclerView dengan wrap_contentsebagai tinggi / lebar karena setiap elemen dimasukkan oleh adaptor bisa mengubah ukuran Recyclertergantung pada barang-barang dimasukkan / dihapus, sehingga, ukuran Recyclerakan berbeda setiap kali kita menambahkan / menghapus item.

Agar lebih jelas, jika kita gunakan

<android.support.v7.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Kita bisa gunakan my_recycler_view.setHasFixedSize(true)

<android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

Kita harus menggunakan my_recycler_view.setHasFixedSize(false), ini berlaku jika kita juga menggunakan wrap_contentlebar

Gastón Saillén
sumber
6

Wen kita set setHasFixedSize(true)pada RecyclerViewyang berarti ukuran pendaur ulang adalah tetap dan tidak terpengaruh oleh isi adaptor. Dan dalam hal onLayoutini tidak dipanggil pada pendaur ulang saat kita memperbarui data adaptor (tapi ada pengecualian).

Mari kita lihat contoh:

RecyclerViewmemiliki RecyclerViewDataObserver( temukan implementasi default di file ini ) dengan beberapa metode, yang terpenting adalah:

void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}

Metode ini disebut jika kita mengatur setHasFixedSize(true)dan memperbarui data adaptor ini melalui: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. Dalam hal ini tidak ada panggilan ke pendaur ulang onLayout, tetapi ada panggilan ke requestLayoutuntuk memperbarui turunannya.

Tetapi jika kita mengatur setHasFixedSize(true)dan memperbarui data adaptor melalui notifyItemChangedmaka ada panggilan ke onChangedefault pendaur ulang RecyclerViewDataObserverdan tidak ada panggilan ke triggerUpdateProcessor. Dalam hal ini pendaur ulang onLayoutdipanggil setiap kali kita menyetel setHasFixedSize trueatau false.

// no calls to triggerUpdateProcessor
@Override
public void onChanged() {
    assertNotInLayoutOrScroll(null);
     mState.mStructureChanged = true;

     processDataSetCompletelyChanged(true);
     if (!mAdapterHelper.hasPendingUpdates()) {
         requestLayout();
     }
}

// calls to triggerUpdateProcessor
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}

Cara memeriksanya sendiri:

Buat kustom RecyclerViewdan timpa:

override fun requestLayout() {
    Log.d("CustomRecycler", "requestLayout is called")
    super.requestLayout()
}

override fun invalidate() {
    Log.d("CustomRecycler", "invalidate is called")
    super.invalidate()
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    Log.d("CustomRecycler", "onLayout is called")
    super.onLayout(changed, l, t, r, b)
}

Setel ukuran pendaur ulang ke match_parent(dalam xml). Coba perbarui data adaptor menggunakan replaceDatadan replaceOne dengan pengaturan setHasFixedSize(true)lalu false.

// onLayout is called every time
fun replaceAll(data: List<String>) {
    dataSet.clear()
    dataSet.addAll(data)
    this.notifyDataSetChanged()
}

// onLayout is called only for setHasFixedSize(false)
fun replaceOne(data: List<String>) {
    dataSet.removeAt(0)
    dataSet.addAll(0, data[0])
    this.notifyItemChanged(0)
}

Dan periksa log Anda.

Log saya:

// for replaceAll
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onMeasure is called
D/CustomRecycler: onMeasure is called
D/CustomRecycler: onLayout
D/CustomRecycler: requestLayout is called
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called

// for replaceOne
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called

Meringkaskan:

Jika kita menyetel setHasFixedSize(true)dan memperbarui data adaptor dengan memberi tahu pengamat dengan cara lain selain memanggil notifyDataSetChanged, maka Anda memiliki kinerja, karena tidak ada panggilan ke onLayoutmetode pendaur ulang .

bitvale.dll
sumber
Apakah Anda menguji dengan RecyclerView of height menggunakan wrap_content atau match_parent?
Lubos Mudrak
3

setHasFixedSize (true) berarti RecyclerView memiliki turunan (item) yang memiliki lebar dan tinggi tetap. Ini memungkinkan RecyclerView untuk mengoptimalkan lebih baik dengan mencari tahu tinggi dan lebar yang tepat dari seluruh daftar berdasarkan adaptor Anda.

Calvin Park
sumber
5
Bukan itu yang disarankan @dangVarmit.
strangetimes
5
Menyesatkan, sebenarnya ini adalah ukuran tampilan Recycler, bukan ukuran konten
Benoit
0

Ini mempengaruhi animasi recyclerview, jika false.. animasi penyisipan dan penghapusan tidak akan ditampilkan. jadi pastikan truejika Anda menambahkan animasi untuk recyclerview.

Alaa AbuZarifa
sumber
0

Jika ukuran RecyclerView (RecyclerView itu sendiri)

... tidak bergantung pada konten adaptor:

mRecyclerView.setHasFixedSize(true);

... tergantung pada konten adaptor:

mRecyclerView.setHasFixedSize(false);
Alok Singh
sumber