了解RecyclerView setHasFixedSize


136

我在理解上有些困难setHasFixedSize()。我知道RecyclerView文档中的大小不变时,它用于优化。

那是什么意思呢?在大多数情况下,ListView尺寸几乎总是固定的。在什么情况下不是固定大小?这是否意味着它在屏幕上占据的实际空间会随着内容的增长而增加?



我发现此答案是有用的,而且非常容易理解[StackOverflow-rv.setHasFixedSize(true); ](stackoverflow.com/questions/28827597/…
Mustafa Hasan

Answers:


115

RecyclerView的一个非常简化的版本具有:

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

此链接描述了为什么致电requestLayout可能会很昂贵。基本上,无论何时插入,移动或删除项目,RecyclerView的大小(宽度和高度)都可能会改变,从而视图层次结构中其他任何视图的大小也可能会改变。如果经常添加或删除项目,这尤其麻烦。

setHasFixedSize当更改适配器的内容而不更改其高度或宽度时,将其设置为true 可以避免不必要的布局传递。


更新: JavaDoc已更新,可以更好地描述该方法的实际作用。

如果RecyclerView可以事先知道RecyclerView的大小不受适配器内容的影响,则可以执行多种优化。RecyclerView仍然可以基于其他因素(例如其父级的大小)来更改其大小,但是此大小计算不能取决于其子级的大小或适配器的内容(适配器中的项目数除外)。

如果您对RecyclerView的使用属于此类别,请将其设置为{@code true}。当适配器内容更改时,它将允许RecyclerView避免使整个布局无效。

@param hasFixedSize如果适配器的更改不能影响RecyclerView的大小,则为true。


162
无论何时添加任何东西,RecyclerView的大小都会改变。setHasFixedSize的作用是(通过用户输入)确保RecyclerView大小的这种变化是恒定的。物品的高度(或宽度)不会改变。添加或删除的每个项目都是相同的。如果您未设置此项,它将检查商品的尺寸是否已更改,且价格昂贵。请澄清一下,因为此答案令人困惑。
阿诺·巴柳

9
@ArnoldB非常好的说明。我什至会认为这是一个独立的答案。
young_souvlaki

4
@ArnoldB-我还是很困惑。您是否建议如果所有子项的宽度/高度均恒定,则应将hasFixedSize设置为true?如果是,那么在运行时可能会删除一些子项(我有滑动以关闭功能)的可能性-设置true可以吗?
美洲虎

1
是。因为项目的宽度和高度没有变化。它只是被添加或删除。添加或删除项目不会更改其大小。
阿诺德·巴柳

3
@ArnoldB我认为项目的大小(宽度/高度)不是问题。它也不会检查项目的大小。它只是告诉RecyclerView requestLayout在更新dataSet之后是否调用。
Kimi Chiu

22

可以确认setHasFixedSize与RecyclerView本身有关,而不是与之适应的每个项目的大小。

现在,您可以android:layout_height="wrap_content"在RecyclerView上使用,它可以使CollapsingToolbarLayout知道在RecyclerView为空时不应折叠。这仅在您setHasFixedSize(false)在RecylcerView上使用时有效。

如果您setHasFixedSize(true)在RecyclerView上使用,即使RecyclerView确实为空,这种防止CollapsingToolbarLayout崩溃的行为也不起作用。

如果setHasFixedSize与项目的大小有关,则当RecyclerView没有项目时,该项目不起作用。


4
我刚刚经历了指向同一方向的经历。将RecyclerView与GridLayoutManager(每行3个项目)一起使用,并且layout_height = wrap_content。当我单击将3个新项目添加到列表中的按钮时,“回收者”视图不会展开以适应新项目。而是,它保持相同的大小,并且查看新项目的唯一方法是滚动它。即使项目具有相同的大小,我也必须删除setHasFixedSize(true)以使其在添加新项目时扩展。
Mateus Gondim '16

我觉得你是对的。hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.因此,即使从文档中更改项目的大小,也可以将其设置为true。
Kimi Chiu

12

ListView有一个类似的命名函数,我认为确实反映了有关单个列表项高度的大小的信息。RecyclerView的文档非常清楚地指出,它是指RecyclerView本身的大小,而不是其项目的大小。

在setHasFixedSize()方法上方的RecyclerView源注释中:

 * 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
但是,如何定义RecyclerView的“大小”?它是仅在屏幕上可见的大小,还是RecyclerView的完整大小(等于(项目高度+填充+间距的总和))?
Vicky Chijwani,2015年

2
确实,这需要更多信息。如果删除项目,并且recyclerview缩小,是否认为尺寸已更改?
Henrique de Sousa 2015年

4
我会认为它就像TextView如何布置自己。如果指定wrap_content,则在设置文本时,TextView可以请求布局通过并更改其在屏幕上占用的空间量。如果指定match_parent或固定尺寸,则TextView将不请求布局通过,因为尺寸是固定的,并且insde文本的数量将永远不会改变占用的空间。RecyclerView是相同的。setHasFixedSize()向RV暗示,它永远不需要根据对适配器项的更改来请求布局传递。
dangVarmit

1
@dangVarmit很好的解释!
howerknea

6

温家宝我们设置setHasFixedSize(true)RecyclerView是手段回收的大小是固定的,不受适配器内容。在这种情况下onLayout,当我们更新适配器的数据时,不会在回收器上调用(但有例外)。

让我们来看这个例子:

RecyclerView有几种方法RecyclerViewDataObserver在此文件中找到默认的象征),主要的是:

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

如果我们setHasFixedSize(true)通过以下方法设置和更新适配器的数据,则会调用此方法notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved。在这种情况下,不会调用回收站的onLayout,但是会调用来requestLayout更新子进程。

但是,如果我们通过设置setHasFixedSize(true)和更新适配器的数据notifyItemChanged,则会调用onChange回收商的默认值,RecyclerViewDataObserver而不会调用triggerUpdateProcessor。在这种情况下,onLayout只要我们设置setHasFixedSize true或,就会调用回收站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();
    }
}

如何自己检查:

创建自定义RecyclerView并覆盖:

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

将回收站大小设置为match_parent(以xml为单位)。尝试使用replaceDatareplaceOne 设置setHasFixedSize(true),然后使用来更新适配器的数据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)
}

并检查您的日志。

我的日志:

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

总结:

如果我们setHasFixedSize(true)通过调用而不是通过其他方式通知观察者来设置和更新适配器的数据notifyDataSetChanged,那么您会有一些表现,因为没有调用recycler onLayout方法。


您是否使用wrap_content或match_parent对RecyclerView进行了高度测试?
Lubos Mudrak

6

如果我们使用RecyclerViewwith match_parent作为height / width,则应添加,setHasFixedSize(true)因为其RecyclerView本身的大小不会更改在其中插入或删除项目的大小。

如果我们有一个带有高度/宽度的RecyclerView,则setHasFixedSize应当为false,wrap_content因为适配器插入的每个元素都可以根据插入/删除的项目更改对象的大小,因此,每次添加/删除时,对象的大小都会不同。项目。RecyclerRecycler

更明确地说,如果我们使用

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

我们可以用 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"/>

我们应该使用my_recycler_view.setHasFixedSize(false),如果我们也将其wrap_content用作宽度


3

setHasFixedSize(true)表示RecyclerView具有固定宽度和高度的子项(项目)。通过根据适配器确定整个列表的确切高度和宽度,这可以使RecyclerView更好地进行优化。


5
那不是@dangVarmit所建议的。
奇怪的时光'16

5
令人误解的是,实际上是回收站的视图大小,而不是内容的大小
Benoit

0

它会影响recyclerview的动画,如果它是false..则不会显示插入和删除动画。因此,请确保true为recyclerview添加了动画。


0

如果RecyclerView的大小(RecyclerView本身)

...不取决于适配器的内容:

mRecyclerView.setHasFixedSize(true);

...取决于适配器的内容:

mRecyclerView.setHasFixedSize(false);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.