理解回收视图 setHasFixedSize

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

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

110017 次浏览

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

来自 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.

SetHasFixedSize (true)表示回收视图的子项具有固定的宽度和高度。通过计算基于您的适配器的整个列表的确切高度和宽度,这允许珊瑚视图更好地进行优化。

非常的回收视图简化版本包括:

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

这个链接 描述了为什么调用 requestLayout可能是昂贵的。基本上,无论何时插入、移动或删除项目,回收视图的大小(宽度和高度)都可能发生变化,而视图层次结构中的任何其他视图的大小也可能发生变化。如果频繁地添加或删除项目,这尤其麻烦。

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


更新: JavaDoc 已经进行了更新,以更好地描述该方法的实际功能。

如果知道 提前设置了回收视图的大小不受适配器的影响 回收视图仍然可以根据其他 因素(例如其父母的大小) ,但这个大小的计算不能 取决于其子级的大小或其适配器的内容(除非 适配器中的项数)。 < p > 如果您使用回收视图 属于此类别,将其设置为{@code true }。它将允许 回收视图,以避免适配器时整个布局失效 内容变化。

如果适配器的更改不能影响 回收视图。

可以确认 setHasFixedSize与回收视图本身相关,而不是适合它的每个项目的大小。

您现在可以在回收视图上使用 android:layout_height="wrap_content",它允许 CollapsingToolbarLayout 知道它不应该在回收视图为空时崩溃。这只有在 RecylcerView 上使用 setHasFixedSize(false)时才有效。

如果在回收视图上使用 setHasFixedSize(true),那么这种防止 CollapsingToolbarLayout 崩溃的行为将不起作用,即使回收视图确实是空的。

如果 setHasFixedSize与项目的大小有关,那么当回收视图没有项目时,它应该不会产生任何效果。

如果是 false的话,它会影响到回收站的动画。.插入和删除动画不会显示。所以确保它是 true以防你添加动画的回收视图。

如果回收视图的大小(回收视图本身)

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

mRecyclerView.setHasFixedSize(true);

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

mRecyclerView.setHasFixedSize(false);

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

让我们来看一个例子:

RecyclerViewRecyclerViewDataObserver(在此文件中查找默认实现)有几种方法,主要重点是:

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。在这种情况下,每当我们设置 setHasFixedSizetruefalse时,就会调用回收器 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方法的调用。

简单解释

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

SetHasFixedSize 如果我们有一个回收视图,其中 wrap_content高度/阔度,那么 setHasFixedSize 应该为 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)

然后,如果我们不使用一个固定的 宽度/高度

<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宽度身高可以改变我们的 RecyclerView的大小。

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

回收视图的大小变化每次您添加的东西,无论是什么。

SetHasFixedSize 所做的是确保(通过用户输入)回收视图大小的这种变化是恒定的。项目的高度(或宽度)不会改变。每个添加或删除的项目将是相同的。

如果你不设置这个,它会检查项目的大小是否已经改变,这是昂贵的

SetHasFixedSize ()指的是 itemView 的大小,或者你可以说视图的大小,所以它不需要处理 list 或 ArrayList,如果你想改变适配器中不同位置的视图的高度和宽度,那么把它设置为 false

当您将 setHasFixedSize ()设置为 true 时,它会告诉系统,迴收视图的高度和宽度不会改变