185

I'm having some trouble understanding setHasFixedSize(). I know that it is used for optimization when the size of RecyclerView doesn't change, from the docs.

What does that mean though? In most common cases a ListView almost always has a fixed size. In what cases would it not be a fixed size? Does it mean that the actual real estate that it occupies on screen grows with the content?

Xaver Kapeller
  • 49,491
  • 11
  • 98
  • 86
SIr Codealot
  • 5,331
  • 9
  • 33
  • 45
  • http://stackoverflow.com/a/40707099/1177959 – Sotti Nov 20 '16 at 17:37
  • I found this answer is helpful and very easy to understand [StackOverflow - rv.setHasFixedSize(true); ](https://stackoverflow.com/questions/28827597/when-do-we-use-the-recyclerview-sethasfixedsize/28828749) – M. H. Jan 14 '19 at 11:42

11 Answers11

139

A very simplified version of RecyclerView has:

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

This link describes why calling requestLayout might be expensive. Basically whenever items are inserted, moved or removed the size (width and height) of RecyclerView might change and in turn the size of any other view in view hierarchy might change. This is particularly troublesome if items are added or removed frequently.

Avoid unnecessary layout passes by setting setHasFixedSize to true when changing the contents of the adapter does not change it's height or the width.


Update: The JavaDoc has been updated to better describe what the method actually does.

RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents. RecyclerView can still change its size based on other factors (e.g. its parent's size) but this size calculation cannot depend on the size of its children or contents of its adapter (except the number of items in the adapter).

If your use of RecyclerView falls into this category, set this to {@code true}. It will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.

@param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.

LukaCiko
  • 4,437
  • 2
  • 26
  • 30
  • 212
    RecyclerView size changes every time you add something no matter what. What setHasFixedSize does is that it makes sure (by user input) that this change of size of RecyclerView is constant. The height (or width) of the item won't change. Every item added or removed will be the same. If you dont set this it will check if the size of the item has changed and thats expensive. Just clarifying because this answer is confusing. – Arnold Balliu May 25 '16 at 18:42
  • 11
    @ArnoldB excellent clarification. I would even argue it as a standalone answer. – young_souvlaki Jun 17 '16 at 16:04
  • 5
    @ArnoldB - I am still confused. Are you suggesting that we should set hasFixedSize to true if the widths/heights of all children are constant ? If yes, what if there is a possibility that some children can be removed at runtime (I have a swipe to dismiss feature) - is it okay to set true ? – dev Jun 30 '16 at 02:32
  • 3
    Yes. Because the width and height of the item is not changing. It is just being added or removed. Adding or removing items does not change their size. – Arnold Balliu Jun 30 '16 at 02:43
  • 3
    @ArnoldB I don't think the size(width/height) of the item is a problem here. It won't check the item's size either. It just tells the RecyclerView to call `requestLayout` or not after the dataSet has been updated. – Kimi Chiu Jan 02 '17 at 10:06
  • What should be its default value(true/false), if we don't write that line? – Meet Vora Jun 14 '17 at 13:06
  • 1
    @ArnoldBalliu RecyclerView size changes every time you add something no matter what - is not true. See my answer: https://stackoverflow.com/a/53261923/5417224 – bitvale Nov 12 '18 at 12:13
  • @bitvale Do your RecyclerViews exist in a universe where phone screens adapt to the size of the RecyclerViews? Of course RecyclerView size changes with every item thats rendered. Respectfully, I am not going to look at your answer. – Arnold Balliu Nov 13 '18 at 07:26
  • @ArnoldBalliu Are your sure? RecyclerView size changes with every item thats rendered? Try to learn more about Views lifecycle, in particular about RecyclerView lifecycle. If RecyclerView have fixed size or match_parent and setHasFixedSize is true, it size not changed while rendering, but there is an exception, as you not see in my answer. – bitvale Nov 13 '18 at 08:06
  • @bitvale Yes. Dynamic items can only be calculated during runtime. Therefore, RecyclerView size changes. Since setHasFixedSize exists we can disable that. When set RecyclerView will calculate its height before rendering and when it renders it does not need to recalculate its size. When an item gets added the RecyclerView knows its own size and expands in a constant manner. Does its size change? Yes. Is this change constant? That depends if you set setHasFixedSize. I have no idea what you are trying to convey that's different from what everyone here has already said. – Arnold Balliu Nov 14 '18 at 00:03
  • @ArnoldBalliu I believe we mean different things by "the size". You're referring to the "virtual" size, i.e. the size of RecyclerView including things not rendered on the screen or don't fit in the parent layout. I (and the documentation) am referring to the actual inflated size. Your reasoning the method being about the size of the children being the same is incorrect. – LukaCiko Oct 06 '20 at 08:21
38

Simple Explanation

If we have a RecyclerView with match_parent as height/width, we should add setHasFixedSize(true) since the size of the RecyclerView itself does not change inserting or deleting items into it.

setHasFixedSize should be false if we have a RecyclerView with wrap_content as height/width because each element inserted by the adapter could change the size of the RecyclerView depending on the items inserted/deleted, so, the size of the RecyclerView will be different each time we add/delete items.

To be more clear, if we use a fixed width/height

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

We can use my_recycler_view.setHasFixedSize(true)

Then if we do not use a fixed width/height

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

We should use my_recycler_view.setHasFixedSize(false) since wrap_content for width or height can change the size of our RecyclerView.

When we talk about RecyclerView setHasFixedSize we are not talking about the quantity of elements inside of it but instead the the size of the View itself. And this size is determined if the items inside of it are different in sizes also, so, if you have one item that is larger than the other inside the recyclerview, the size is not fixed for the RecyclerView.

Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77
31

Can confirm setHasFixedSize relates to the RecyclerView itself, and not the size of each item adapted to it.

You can now use android:layout_height="wrap_content" on a RecyclerView, which, among other things, allows a CollapsingToolbarLayout to know it should not collapse when the RecyclerView is empty. This only works when you use setHasFixedSize(false) on the RecylcerView.

If you use setHasFixedSize(true) on the RecyclerView, this behavior to prevent the CollapsingToolbarLayout from collapsing does not work, even though the RecyclerView is indeed empty.

If setHasFixedSize was related to the size of items, it shouldn't have any effect when the RecyclerView has no items.

Kevin
  • 1,829
  • 1
  • 21
  • 22
  • 5
    I've just had an experience that points to the same direction. Using a RecyclerView with a GridLayoutManager(3 items per row) and layout_height = wrap_content. When I click a button that adds 3 new items to the list, the recycler view does not expand to fit the new items. Rather, it maintains the same size, and the only way to see the new items is scrolling it. Even though the items have the same size, I had to remove `setHasFixedSize(true)` to make it expand when new items are added. – Mateus Gondim Oct 31 '16 at 19:49
  • I think you're right. From the document, `hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.` So even if the size of the item will change, you still can set this to true. – Kimi Chiu Jan 02 '17 at 09:38
15

The ListView had a similar named function that I think did reflect info about the size of the individual list item heights. The documentation for RecyclerView pretty clearly states it is referring to the size of the RecyclerView itself, not the size of its items.

From the RecyclerView source comment above the setHasFixedSize() method:

 * 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
  • 5,641
  • 2
  • 22
  • 24
  • 16
    But how is RecyclerView's "size" defined? Is it the size visible on screen only, or the full size of the RecyclerView, which equals (sum of item heights + padding + spacing)? – Vicky Chijwani May 14 '15 at 18:37
  • 2
    Indeed, this needs more info. If you remove items and the recyclerview shrinks, is it considered that is had changed size? – Henrique de Sousa Jul 03 '15 at 17:53
  • 5
    I would think of it like how a TextView can lay itself out. If you specify wrap_content, then when you set text the TextView can request a layout pass and change the amount of space it occupies on the screen. If you specify match_parent or a fixed dimension, then TextView will not request a layout pass because the size is fixed and the amount of text insde will never change the amount of space occupied. RecyclerView is the same. setHasFixedSize() hints to RV it should never need to request layout passes based on changes to the adapter items. – dangVarmit Jul 06 '15 at 17:56
  • 1
    @dangVarmit nice explanation! – howerknea Jun 27 '16 at 08:55
7

When we set setHasFixedSize(true) on RecyclerView that means recycler's size is fixed and is not affected by the adapter contents. And in this case onLayout is not called on recycler when we update the adaptrer's data (but there is an exception).

Let's go to the example:

RecyclerView has a RecyclerViewDataObserver (find default implemntation in this file) with several methods, the main important is:

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

This method is called if we set setHasFixedSize(true) and update an adapter's data via: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. In this case there is no calls to the recycler's onLayout, but there is calls to requestLayout for updating childs.

But if we set setHasFixedSize(true) and update an adapter's data via notifyItemChanged then there is call to onChange of the recycler's default RecyclerViewDataObserver and no calls to triggerUpdateProcessor. In this case the recycler onLayout is called whenever we set setHasFixedSize true or 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();
    }
}

How to check by yourself:

Create custom RecyclerView and override:

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

Set the recycler size to match_parent (in xml). Try to update adapter's data using replaceData and replaceOne with seting setHasFixedSize(true) and then 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)
}

And check your log.

My log:

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

Summarize:

If we set setHasFixedSize(true) and update adapter's data with notifying an observer in some other way than calling notifyDataSetChanged, then you have some perfomance, because the is no calls to the recycler onLayout method.

Julian A.
  • 10,928
  • 16
  • 67
  • 107
bitvale
  • 1,959
  • 1
  • 20
  • 27
1

It affects the animations of the recyclerview, if it's false.. the insert and remove animations won't show. so make sure it's true in case you added animation for the recyclerview.

Alaa AbuZarifa
  • 1,171
  • 20
  • 39
1

If the size of the RecyclerView (the RecyclerView itself)

... does not depend on the adapter content:

mRecyclerView.setHasFixedSize(true);

...depends on the adapter content:

mRecyclerView.setHasFixedSize(false);
Nic3500
  • 8,144
  • 10
  • 29
  • 40
Alok Singh
  • 640
  • 4
  • 14
0

RecyclerView size changes every time you add something no matter what.

What setHasFixedSize does is that it makes sure (by user input) that this change of size of RecyclerView is constant. The height (or width) of the item won't change. Every item added or removed will be the same.

If you don't set this it will check if the size of the item has changed and thats expensive. (From a comment)

mathematics-and-caffeine
  • 1,664
  • 2
  • 15
  • 19
0

setHasFixedSize() refers to the size of the itemView or you can say the view size , so it has nothing to deal with list or ArrayList , if you want to change the height and width of the view for different positions in the adapter then put it to false

0

when you set setHasFixedSize() to true, it tell the system that recyclerview's height and width will not change

thelearner
  • 46
  • 3
-1

setHasFixedSize(true) means the RecyclerView has children (items) that has fixed width and height. This allows the RecyclerView to optimize better by figuring out the exact height and width of the entire list based on the your adapter.

Calvin Park
  • 1,144
  • 1
  • 12
  • 21