Improve FlatList estimates for items not yet laid out (#46487)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/46487

Changelog: [General][Fixed] Fixed accuracy of FlatList estimations to determine what elements are visible in the rendering window.

This fixes a bug in FlatList where it thinks that some elements are visible in the rendering window, when they're not. Specifically, if a cell hasn't been laid out yet, it ignores all the information it already has on the ones that had, and estimates its position and offset based on the estimated size of the cells. In this case, if the first element has a larger offset because the list has a header, that offset is ignored in this case.

One observed result of this is that in a list where there's a header and a single cell that occupy the whole rendering window, FlatList thinks it needs to pre-render an additional element because the header is ignored.

Reviewed By: NickGerleman

Differential Revision: D62649060

fbshipit-source-id: 437bae79916707ca1d08784190508a9f7e36688e
This commit is contained in:
Rubén Norte
2024-09-13 16:18:33 -07:00
committed by Facebook GitHub Bot
parent 70188fe222
commit 40aaeb7181
2 changed files with 33 additions and 10 deletions
@@ -167,6 +167,30 @@ export default class ListMetricsAggregator {
// check for invalid frames due to row re-ordering
return frame;
} else {
let offset;
const highestMeasuredCellIndex = this.getHighestMeasuredCellIndex();
if (highestMeasuredCellIndex < index) {
// If any of the cells before this one have been laid out already, we
// should use that information in the estimations.
// This is important because if the list has a header, the initial cell
// will have a larger offset that we should take into account here.
const highestMeasuredCellFrame = this.getCellMetrics(
highestMeasuredCellIndex,
props,
);
if (highestMeasuredCellFrame) {
offset =
highestMeasuredCellFrame.offset +
highestMeasuredCellFrame.length +
this._averageCellLength * (index - highestMeasuredCellIndex - 1);
}
}
if (offset == null) {
offset = this._averageCellLength * index;
}
const {data, getItemCount} = props;
invariant(
index >= 0 && index < getItemCount(data),
@@ -174,7 +198,7 @@ export default class ListMetricsAggregator {
);
return {
length: this._averageCellLength,
offset: this._averageCellLength * index,
offset,
index,
isMounted: false,
};
@@ -221,7 +221,7 @@ describe('ListMetricsAggregator', () => {
height: 10,
width: 5,
x: 0,
y: 0,
y: 100,
},
});
@@ -233,7 +233,7 @@ describe('ListMetricsAggregator', () => {
height: 20,
width: 5,
x: 0,
y: 10,
y: 110,
},
});
@@ -241,7 +241,7 @@ describe('ListMetricsAggregator', () => {
expect(listMetrics.getCellMetricsApprox(2, props)).toEqual({
index: 2,
length: 15,
offset: 30,
offset: 130,
isMounted: false,
});
});
@@ -481,7 +481,7 @@ describe('ListMetricsAggregator', () => {
it('estimates RTL metrics of unmeasured cell', () => {
const listMetrics = new ListMetricsAggregator();
const orientation = {horizontal: true, rtl: false};
const orientation = {horizontal: true, rtl: true};
const props: CellMetricProps = {
data: [1, 2, 3, 4, 5],
getItemCount: () => nullthrows(props.data).length,
@@ -500,7 +500,7 @@ describe('ListMetricsAggregator', () => {
layout: {
height: 5,
width: 10,
x: 90,
x: 70,
y: 0,
},
});
@@ -512,23 +512,22 @@ describe('ListMetricsAggregator', () => {
layout: {
height: 5,
width: 20,
x: 70,
x: 50,
y: 0,
},
});
expect(listMetrics.getCellMetrics(2, props)).toBeNull();
expect(listMetrics.getCellMetricsApprox(2, props)).toEqual({
index: 2,
length: 15,
offset: 30,
offset: 50,
isMounted: false,
});
});
it('uses getItemLayout for RTL metrics of unmeasured cell', () => {
const listMetrics = new ListMetricsAggregator();
const orientation = {horizontal: true, rtl: false};
const orientation = {horizontal: true, rtl: true};
const props: CellMetricProps = {
data: [1, 2, 3, 4, 5],
getItemCount: () => nullthrows(props.data).length,