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.