【问题标题】:Understanding RecyclerView setHasFixedSize了解 RecyclerView setHasFixedSize
【发布时间】:2015-02-25 00:49:16
【问题描述】:

我在理解 setHasFixedSize() 时遇到了一些麻烦。我知道当RecyclerView 的大小没有改变时,它用于优化,来自文档。

那是什么意思?在最常见的情况下,ListView 几乎总是具有固定大小。在什么情况下它不是固定大小?这是否意味着它在屏幕上占据的实际空间随着内容的增长而增长?

【问题讨论】:

标签: android android-5.0-lollipop android-recyclerview


【解决方案1】:

一个非常简化版的 RecyclerView 有:

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

This link 描述了为什么调用requestLayout 可能很昂贵。基本上,每当插入、移动或移除项目时,RecyclerView 的大小(宽度和高度)可能会发生变化,进而视图层次结构中任何其他视图的大小可能会发生变化。如果经常添加或删除项目,这尤其麻烦。

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


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

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

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

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

【讨论】:

  • RecyclerView 的大小在每次添加时都会发生变化。 setHasFixedSize 所做的是确保(通过用户输入) RecyclerView 大小的这种变化是恒定的。项目的高度(或宽度)不会改变。添加或删除的每个项目都是相同的。如果您不设置此项,它将检查项目的大小是否已更改并且价格昂贵。只是澄清一下,因为这个答案令人困惑。
  • @ArnoldB 很好的说明。我什至会认为它是一个独立的答案。
  • @ArnoldB - 我仍然很困惑。如果所有孩子的宽度/高度都是恒定的,您是否建议我们将 hasFixedSize 设置为 true ?如果是,如果有可能在运行时删除一些孩子(我有滑动关闭功能)- 设置 true 可以吗?
  • 是的。因为项目的宽度和高度没有改变。它只是被添加或删除。添加或删除项目不会改变它们的大小。
  • @ArnoldB 我认为项目的大小(宽度/高度)在这里不是问题。它也不会检查项目的大小。它只是告诉 RecyclerView 在数据集更新后是否调用requestLayout
【解决方案2】:

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

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

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

如果setHasFixedSize与item的大小有关,那么在RecyclerView没有item的情况下应该没有任何影响。

【讨论】:

  • 我刚刚经历了一次指向同一个方向的经历。使用带有 GridLayoutManager(每行 3 个项目)和 layout_height = wrap_content 的 RecyclerView。当我单击将 3 个新项目添加到列表中的按钮时,回收站视图不会扩展以适应新项目。相反,它保持相同的大小,查看新项目的唯一方法是滚动它。即使这些项目的大小相同,我也必须删除 setHasFixedSize(true) 以使其在添加新项目时扩展。
  • 我认为你是对的。从文档中,hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView. 所以即使item的大小会发生变化,你仍然可以将这个设置为true。
【解决方案3】:

简单说明

如果我们有一个RecyclerViewmatch_parent 作为高度/宽度,我们应该添加setHasFixedSize(true),因为RecyclerView 本身的大小不会改变插入或删除项目到它.

setHasFixedSize 如果我们有一个带有wrap_content 作为height/width 的 RecyclerView,则应该为 false,因为适配器插入的每个元素都可能改变 RecyclerView 的大小取决于插入/删除的项目,因此,每次我们添加/删除项目时,RecyclerView 的大小都会有所不同。

更清楚地说,如果我们使用固定的宽度/高度

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

我们可以使用my_recycler_view.setHasFixedSize(true)

那么如果我们不使用固定的width/height

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

我们应该使用my_recycler_view.setHasFixedSize(false),因为wrap_content 用于widthheight 可以改变我们RecyclerView 的大小。

当我们谈论 RecyclerView setHasFixedSize 时,我们不是在谈论其中元素的数量,而是在谈论 View 本身的大小。

【讨论】:

    【解决方案4】:

    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.
    

    【讨论】:

    • 但是 RecyclerView 的“大小”是如何定义的呢?它是仅在屏幕上可见的大小,还是 RecyclerView 的完整大小,等于(项目高度的总和 + 填充 + 间距)?
    • 确实,这需要更多信息。如果您删除项目并且recyclerview缩小,是否认为已经改变了大小?
    • 我会认为它就像 TextView 如何布置自己一样。如果您指定 wrap_content,那么当您设置文本时,TextView 可以请求布局传递并更改它在屏幕上占用的空间量。如果您指定 match_parent 或固定尺寸,那么 TextView 将不会请求布局传递,因为尺寸是固定的,并且 insde 的文本量永远不会改变占用的空间量。 RecyclerView 也是一样。 setHasFixedSize() 向 RV 提示它永远不需要根据对适配器项的更改来请求布局传递。
    • @dangVarmit 很好的解释!
    【解决方案5】:

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

    我们来看例子:

    RecyclerView 有一个RecyclerViewDataObserver (find default implemntation in this file) 有几种方法,主要的是:

    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 更新适配器的数据,那么会调用回收器默认RecyclerViewDataObserveronChange 而不会调用triggerUpdateProcessor。在这种情况下,每当我们设置 setHasFixedSize truefalse 时,都会调用回收器 onLayout

    // 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 以外的其他方式通知观察者,那么您有一些性能,因为没有调用回收器onLayout 方法。

    【讨论】:

    • 您是否使用 wrap_content 或 match_parent 对高度的 RecyclerView 进行了测试?
    【解决方案6】:

    setHasFixedSize(true) 表示 RecyclerView 具有固定宽度和高度的子项(项目)。这允许 RecyclerView 通过根据您的适配器计算出整个列表的确切高度和宽度来更好地优化。

    【讨论】:

    • 这不是@dangVarmit 所建议的。
    • 误导,实际上是 Recycler 视图大小,而不是内容大小
    【解决方案7】:

    它会影响recyclerview 的动画,如果它是false.. 插入和删除动画将不会显示。所以确保它是true,以防你为recyclerview添加了动画。

    【讨论】:

      【解决方案8】:

      如果RecyclerView的大小(RecyclerView本身)

      ...不依赖于适配器内容:

      mRecyclerView.setHasFixedSize(true);
      

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

      mRecyclerView.setHasFixedSize(false);
      

      【讨论】:

        【解决方案9】:

        RecyclerView 的大小在每次添加时都会发生变化。

        setHasFixedSize 的作用是确保(通过用户输入)RecyclerView 大小的这种变化是恒定的。项目的高度(或宽度)不会改变。添加或删除的每个项目都是相同的。

        如果您不设置此项,它将检查项目的大小是否已更改并且价格昂贵。 (来自评论)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多