Skip to content

Commit

Permalink
Improve FlatList estimates for items not yet laid out (#46487)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
rubennorte authored and facebook-github-bot committed Sep 13, 2024
1 parent 70188fe commit 40aaeb7
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 10 deletions.
26 changes: 25 additions & 1 deletion packages/virtualized-lists/Lists/ListMetricsAggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,38 @@ 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),
'Tried to get frame for out of range index ' + index,
);
return {
length: this._averageCellLength,
offset: this._averageCellLength * index,
offset,
index,
isMounted: false,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ describe('ListMetricsAggregator', () => {
height: 10,
width: 5,
x: 0,
y: 0,
y: 100,
},
});

Expand All @@ -233,15 +233,15 @@ describe('ListMetricsAggregator', () => {
height: 20,
width: 5,
x: 0,
y: 10,
y: 110,
},
});

expect(listMetrics.getCellMetrics(2, props)).toBeNull();
expect(listMetrics.getCellMetricsApprox(2, props)).toEqual({
index: 2,
length: 15,
offset: 30,
offset: 130,
isMounted: false,
});
});
Expand Down Expand Up @@ -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,
Expand All @@ -500,7 +500,7 @@ describe('ListMetricsAggregator', () => {
layout: {
height: 5,
width: 10,
x: 90,
x: 70,
y: 0,
},
});
Expand All @@ -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,
Expand Down

0 comments on commit 40aaeb7

Please sign in to comment.