Hiểu RecyclerView setHasFixedSize


136

Tôi đang gặp một số khó khăn để hiểu setHasFixedSize(). Tôi biết rằng nó được sử dụng để tối ưu hóa khi kích thước của RecyclerViewkhông thay đổi, từ các tài liệu.

Điều đó có nghĩa là gì? Trong hầu hết các trường hợp phổ biến, ListViewhầu như luôn luôn có một kích thước cố định. Trong trường hợp nào nó sẽ không phải là một kích thước cố định? Có nghĩa là bất động sản thực tế mà nó chiếm trên màn hình phát triển cùng với nội dung?



Tôi thấy câu trả lời này rất hữu ích và rất dễ hiểu [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/õ )
Mustafa Hasan

Câu trả lời:


115

Một phiên bản rất đơn giản của RecyclerView có:

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

Liên kết này mô tả lý do tại sao gọi điện thoại requestLayoutcó thể đắt tiền. Về cơ bản, bất cứ khi nào các mục được chèn, di chuyển hoặc loại bỏ kích thước (chiều rộng và chiều cao) của RecyclerView có thể thay đổi và đến lượt kích thước của bất kỳ chế độ xem nào khác trong phân cấp chế độ xem có thể thay đổi. Điều này đặc biệt rắc rối nếu các mục được thêm hoặc xóa thường xuyên.

Tránh bố trí không cần thiết bằng cách đặt setHasFixedSizethành đúng khi thay đổi nội dung của bộ chuyển đổi không thay đổi chiều cao hoặc chiều rộng.


Cập nhật: JavaDoc đã được cập nhật để mô tả tốt hơn những gì phương thức thực sự làm.

RecyclerView có thể thực hiện một số tối ưu hóa nếu có thể biết trước rằng kích thước của RecyclerView không bị ảnh hưởng bởi nội dung bộ điều hợp. RecyclerView vẫn có thể thay đổi kích thước của nó dựa trên các yếu tố khác (ví dụ kích thước của cha mẹ) nhưng tính toán kích thước này có thể phụ thuộc vào kích thước của con hoặc nội dung của bộ điều hợp (ngoại trừ số lượng vật phẩm trong bộ chuyển đổi).

Nếu việc sử dụng RecyclerView của bạn rơi vào danh mục này, hãy đặt mục này thành {@code true}. Nó sẽ cho phép RecyclerView tránh làm mất hiệu lực toàn bộ bố cục khi nội dung bộ điều hợp của nó thay đổi.

@param hasFixedSize true nếu thay đổi bộ điều hợp không thể ảnh hưởng đến kích thước của RecyclerView.


162
Kích thước RecyclerView thay đổi mỗi khi bạn thêm một cái gì đó không có vấn đề gì. Điều setHasFixedSize làm là nó đảm bảo (theo đầu vào của người dùng) rằng sự thay đổi kích thước này của RecyclerView là không đổi. Chiều cao (hoặc chiều rộng) của vật phẩm sẽ không thay đổi. Mỗi mục được thêm hoặc loại bỏ sẽ giống nhau. Nếu bạn không đặt cái này, nó sẽ kiểm tra xem kích thước của vật phẩm đã thay đổi và có đắt không. Chỉ cần làm rõ vì câu trả lời này là khó hiểu.
Arnold Balliu

9
@ArnoldB làm rõ tuyệt vời. Tôi thậm chí sẽ tranh luận nó như là một câu trả lời độc lập.
young_souvlaki

4
@ArnoldB - Tôi vẫn còn bối rối. Bạn có gợi ý rằng chúng ta nên đặt hasFixedSize thành true nếu chiều rộng / chiều cao của tất cả trẻ em không đổi? Nếu có, điều gì xảy ra nếu có khả năng một số trẻ em có thể bị xóa trong thời gian chạy (tôi có tính năng vuốt để loại bỏ) - liệu có ổn không khi đặt đúng?
Jaguar

1
Đúng. Bởi vì chiều rộng và chiều cao của vật phẩm không thay đổi. Nó chỉ được thêm hoặc loại bỏ. Thêm hoặc xóa các mục không thay đổi kích thước của chúng.
Arnold Balliu

3
@ArnoldB Tôi không nghĩ kích thước (chiều rộng / chiều cao) của vật phẩm là vấn đề ở đây. Nó sẽ không kiểm tra kích thước của mặt hàng. Nó chỉ báo cho RecyclerView gọi requestLayouthay không sau khi dữ liệu đã được cập nhật.
Kimi Chiu

22

Có thể xác nhận setHasFixedSizeliên quan đến chính RecyclerView, và không phải kích thước của từng mục thích ứng với nó.

Bây giờ bạn có thể sử dụng android:layout_height="wrap_content"trên RecyclerView, trong số những thứ khác, cho phép CollapsingToolbarLayout biết rằng nó không bị sập khi RecyclerView trống. Điều này chỉ hoạt động khi bạn sử dụng setHasFixedSize(false)trên RecylcerView.

Nếu bạn sử dụng setHasFixedSize(true)trên RecyclerView, hành vi này để ngăn CollapsingToolbarLayout sụp đổ không hoạt động, mặc dù RecyclerView thực sự trống rỗng.

Nếu setHasFixedSizecó liên quan đến kích thước của vật phẩm, nó sẽ không có bất kỳ ảnh hưởng nào khi RecyclerView không có vật phẩm.


4
Tôi vừa có một trải nghiệm chỉ ra cùng một hướng. Sử dụng RecyclerView với GridLayoutManager (3 mục mỗi hàng) và layout_height = quấn_content. Khi tôi nhấp vào nút thêm 3 mục mới vào danh sách, chế độ xem của trình tái chế không mở rộng để phù hợp với các mục mới. Thay vào đó, nó duy trì cùng kích thước và cách duy nhất để xem các mục mới là cuộn nó. Mặc dù các mặt hàng có cùng kích thước, tôi đã phải loại bỏ setHasFixedSize(true)để làm cho nó mở rộng khi các mặt hàng mới được thêm vào.
Mateus Gondim

Tôi nghĩ bạn đúng. Từ tài liệu, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.vì vậy ngay cả khi kích thước của vật phẩm sẽ thay đổi, bạn vẫn có thể đặt giá trị này thành đúng.
Kimi Chiu

12

ListView có một chức năng được đặt tên tương tự mà tôi nghĩ đã phản ánh thông tin về kích thước của chiều cao mục danh sách riêng lẻ. Tài liệu về RecyclerView nói rõ rằng nó đề cập đến kích thước của chính RecyclerView, chứ không phải kích thước của các mục của nó.

Từ nhận xét nguồn RecyclerView ở trên phương thức 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.

16
Nhưng "kích thước" của RecyclerView được định nghĩa như thế nào? Đây có phải là kích thước chỉ hiển thị trên màn hình hay kích thước đầy đủ của RecyclerView, bằng với (tổng chiều cao của vật phẩm + phần đệm + khoảng cách)?
Vicky Chijwani

2
Thật vậy, điều này cần thêm thông tin. Nếu bạn loại bỏ các mục và recyclerview co lại, nó có được coi là đã thay đổi kích thước không?
Henrique de Sousa

4
Tôi sẽ nghĩ về nó giống như cách một TextView có thể tự đặt ra. Nếu bạn chỉ định Wra_content, thì khi bạn đặt văn bản, TextView có thể yêu cầu vượt qua bố cục và thay đổi dung lượng mà nó chiếm trên màn hình. Nếu bạn chỉ định match_parent hoặc kích thước cố định, thì TextView sẽ không yêu cầu vượt qua bố cục vì kích thước được cố định và lượng văn bản inde sẽ không bao giờ thay đổi lượng không gian bị chiếm dụng. RecyclerView là như nhau. gợi ý setHasFixedSize () cho RV, không bao giờ cần phải yêu cầu chuyển bố cục dựa trên các thay đổi đối với các mục của bộ điều hợp.
dangVarmit

1
@dangVarmit giải thích hay!
howerknea

6

Chúng tôi đặt setHasFixedSize(true)trên RecyclerViewđiều đó có nghĩa là kích thước của bộ tái chế được cố định và không bị ảnh hưởng bởi nội dung của bộ chuyển đổi. Và trong trường hợp onLayoutnày không được gọi trên bộ tái chế khi chúng tôi cập nhật dữ liệu của bộ điều hợp (nhưng có một ngoại lệ).

Hãy xem ví dụ:

RecyclerViewcó một RecyclerViewDataObserver( tìm ẩn ý mặc định trong tệp này ) với một số phương thức, quan trọng chính là:

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

Phương pháp này được gọi nếu chúng ta thiết lập setHasFixedSize(true)và cập nhật dữ liệu của bộ điều hợp thông qua : notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. Trong trường hợp này, không có cuộc gọi nào đến người tái chế onLayout, nhưng có cuộc gọi để requestLayoutcập nhật trẻ em.

Nhưng nếu chúng ta thiết lập setHasFixedSize(true)và cập nhật dữ liệu của bộ điều hợp thông qua notifyItemChangedthì sẽ có cuộc gọi đến onChangemặc định của người tái chế RecyclerViewDataObservervà không có cuộc gọi nào triggerUpdateProcessor. Trong trường hợp này, trình tái chế onLayoutđược gọi bất cứ khi nào chúng ta đặt setHasFixedSize truehoặc 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();
    }
}

Cách tự kiểm tra:

Tạo tùy chỉnh RecyclerViewvà ghi đè:

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)
}

Đặt kích thước của trình tái chế thành match_parent(tính bằng xml). Cố gắng cập nhật dữ liệu của bộ điều hợp bằng cách sử dụng replaceDatareplaceOne với thiết lập setHasFixedSize(true)và sau đó 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)
}

Và kiểm tra nhật ký của bạn.

Nhật ký của tôi:

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

Tóm tắt:

Nếu chúng tôi thiết lập setHasFixedSize(true)và cập nhật dữ liệu của bộ điều hợp với thông báo cho người quan sát theo một cách khác ngoài cách gọi notifyDataSetChanged, thì bạn có một số sai sót, bởi vì đó không phải là cuộc gọi đến onLayoutphương thức tái chế .


Bạn đã thử nghiệm với RecyclerView về chiều cao bằng cách sử dụng quấn_content hoặc match_parent chưa?
Lubos Mudrak

6

Nếu chúng ta có một RecyclerViewbằng match_parentnhư chiều cao / chiều rộng , chúng ta nên thêm setHasFixedSize(true)vì kích thước của RecyclerViewnó không thay đổi cách chèn hoặc xóa các mục vào nó.

setHasFixedSize phải là sai lầm nếu chúng ta có một RecyclerView với wrap_contentnhư chiều cao / chiều rộng từ mỗi phần tử chèn vào bởi adapter có thể thay đổi kích thước của Recyclerphụ thuộc vào mặt hàng được chèn / xóa, vì vậy, kích thước của Recyclersẽ khác nhau mỗi lần chúng tôi thêm / xóa mặt hàng.

Để rõ ràng hơn, nếu chúng ta sử dụng

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

Chúng ta có thể sử dụng 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"/>

Chúng ta nên sử dụng my_recycler_view.setHasFixedSize(false), điều này áp dụng nếu chúng ta cũng sử dụng wrap_contentnhư chiều rộng


3

setHasFixedSize (true) có nghĩa là RecyclerView có con (các mục) có chiều rộng và chiều cao cố định. Điều này cho phép RecyclerView tối ưu hóa tốt hơn bằng cách tìm ra chiều cao và chiều rộng chính xác của toàn bộ danh sách dựa trên bộ điều hợp của bạn.


5
Đó không phải là những gì @dangVarmit đã đề xuất.
lạ lùng

5
Gây hiểu lầm, đó thực sự là kích thước khung nhìn Recycler, không phải kích thước của nội dung
Benoit

0

Nó ảnh hưởng đến hình ảnh động của recyclerview, nếu đó là false.. hình ảnh chèn và xóa sẽ không hiển thị. vì vậy hãy chắc chắn rằng truetrong trường hợp bạn đã thêm hình ảnh động cho recyclerview.


0

Nếu kích thước của RecyclerView (chính RecyclerView)

... không phụ thuộc vào nội dung bộ điều hợp:

mRecyclerView.setHasFixedSize(true);

... phụ thuộc vào nội dung bộ điều hợp:

mRecyclerView.setHasFixedSize(false);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.