了解RecyclerView setHasFixedSize

SIr*_*lot 120 android android-5.0-lollipop android-recyclerview

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

这意味着什么?在大多数常见情况下,ListView几乎总是具有固定的大小.在什么情况下它不是固定的大小?这是否意味着它在屏幕上占据的实际房地产随着内容的增长而增长?

Luk*_*iko 101

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

void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();
}
Run Code Online (Sandbox Code Playgroud)

此链接描述了为什么呼叫requestLayout可能很昂贵.基本上每当插入,移动或移除项目时,RecyclerView的大小(宽度和高度)可能会发生变化,反过来,视图层次结构中任何其他视图的大小也可能会发生变化.如果经常添加或删除项目,这尤其麻烦.

setHasFixedSize当更改适配器的内容不改变其高度或宽度时,通过设置为true 避免不必要的布局传递.


更新: JavaDoc已更新,以更好地描述方法实际执行的操作.

如果RecyclerView可以提前知道RecyclerView的大小不受适配器内容的影响,则可以执行多项优化.RecyclerView仍然可以根据其他因素(例如其父级的大小)更改其大小,但此大小计算不能取决于其子级的大小或其适配器的内容(除了适配器中的项目数).

如果您使用RecyclerView属于此类别,请将其设置为{@code true}.它允许RecyclerView在其适配器内容发生变化时避免使整个布局无效.

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

  • 每次添加内容时,RecyclerView大小都会发生变化.setHasFixedSize的作用是确保(通过用户输入)RecyclerView的这种大小变化是不变的.项目的高度(或宽度)不会改变.添加或删除的每个项目都是相同的.如果你没有设置它,它将检查项目的大小是否已经改变,这是昂贵的.只是澄清,因为这个答案令人困惑. (132认同)
  • @ArnoldB出色的澄清.我甚至认为这是一个独立的答案. (9认同)
  • @ArnoldB-我还是很困惑。您是否建议如果所有子项的宽度/高度均恒定,则应将hasFixedSize设置为true?如果是,那么在运行时可能会删除一些子项(我具有轻击即可关闭功能)的可能性-设置true可以吗? (3认同)
  • @ArnoldB 我不认为项目的大小(宽度/高度)在这里是一个问题。它也不会检查项目的大小。它只是告诉 RecyclerView 在数据集更新后是否调用 `requestLayout`。 (3认同)
  • 是的。因为项目的宽度和高度没有变化。它只是被添加或删除。添加或删除项目不会改变它们的大小。 (2认同)

Kev*_*vin 20

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

您现在可以android:layout_height="wrap_content"在RecyclerView上使用,除其他外,它允许CollapsingToolbarLayout知道它在RecyclerView为空时不应该崩溃.这仅适用setHasFixedSize(false)于在RecylcerView上使用的情况.

如果setHasFixedSize(true)在RecyclerView上使用,则此行为可防止CollapsingToolbarLayout崩溃,即使RecyclerView确实为空,也不起作用.

如果setHasFixedSize与物品的大小有关,则当RecyclerView没有物品时,它应该没有任何效果.

  • 我刚刚有过一次指向同一方向的经历.使用带有GridLayoutManager的RecyclerView(每行3个项目)和layout_height = wrap_content.当我单击一个向列表中添加3个新项目的按钮时,回收站视图不会展开以适应新项目.相反,它保持相同的大小,并且查看新项目的唯一方法是滚动它.即使项目具有相同的大小,我也必须删除`setHasFixedSize(true)`以使其在添加新项目时展开. (3认同)

Gas*_*lén 18

简单说明

如果我们有一个RecyclerViewwith match_parentas height/width,我们应该添加,setHasFixedSize(true)因为RecyclerView它本身的大小不会改变向其中插入或删除项目。

setHasFixedSize如果我们有一个高度/宽度wrap_content高度/宽度的 RecyclerView 应该是假的,因为适配器插入的每个元素都可以Recycler根据插入/删除的项目改变 的大小,因此,Recycler每次我们添加/删除的大小都会不同项目。

更清楚地说,如果我们使用

<android.support.v7.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
Run Code Online (Sandbox Code Playgroud)

我们可以用 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"/>
Run Code Online (Sandbox Code Playgroud)

那么,我们应该使用my_recycler_view.setHasFixedSize(false),如果我们使用wrap_content宽度高度在我们的RecyclerView


dan*_*mit 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.
Run Code Online (Sandbox Code Playgroud)

  • 但是如何定义RecyclerView的"大小"?是仅在屏幕上可见的尺寸,还是RecyclerView的全尺寸,等于(物品高度+填充+间距的总和)? (14认同)
  • 我会想到它就像TextView如何展现出来一样.如果指定wrap_content,则在设置文本时,TextView可以请求布局传递并更改它在屏幕上占用的空间量.如果指定match_parent或固定维度,则TextView将不会请求布局传递,因为大小已修复且文本insde的数量永远不会更改占用的空间量.RecyclerView是一样的.setHasFixedSize()提示RV,它永远不需要根据适配器项的更改请求布局传递. (4认同)
  • 确实,这需要更多信息。如果删除项目,并且recyclerview缩小,是否认为尺寸已更改? (2认同)

bit*_*ale 7

当我们设置setHasFixedSize(true)RecyclerView,意味着回收者的大小是固定的,不受适配器内容的影响。在这种情况下onLayout,当我们更新适配器的数据时不会在回收器上调用(但有一个例外)。

我们来看例子:

RecyclerView有一个RecyclerViewDataObserver在这个文件中找到默认实现)有几种方法,主要重要的是:

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

如果我们通过以下方式设置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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如何自行检查:

创建自定义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)
}
Run Code Online (Sandbox Code Playgroud)

将回收器大小设置为match_parent(以 xml 格式)。尝试使用适配器的数据更新replaceData,并replaceOne 与塞汀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)
}
Run Code Online (Sandbox Code Playgroud)

并检查您的日志。

我的日志:

// 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
Run Code Online (Sandbox Code Playgroud)

总结:

如果我们setHasFixedSize(true)通过调用以外的其他方式通知观察者来设置和更新适配器的数据notifyDataSetChanged,那么您将获得一些性能,因为没有调用回收器onLayout方法。