From dd58621650789a5aba82a98687b19cc48aaef5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Fri, 13 Sep 2024 10:59:24 -0700 Subject: [PATCH] Improve FlatList estimates for items not yet laid out Summary: 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. Differential Revision: D62649060 --- .../Lists/ListMetricsAggregator.js | 26 ++++++++++++++++++- .../__tests__/ListMetricsAggregator-test.js | 10 +++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/virtualized-lists/Lists/ListMetricsAggregator.js b/packages/virtualized-lists/Lists/ListMetricsAggregator.js index a7972390211f17..4fb8704d2c919f 100644 --- a/packages/virtualized-lists/Lists/ListMetricsAggregator.js +++ b/packages/virtualized-lists/Lists/ListMetricsAggregator.js @@ -167,6 +167,30 @@ export default class ListMetricsAggregator { // check for invalid frames due to row re-ordering return frame; } else { + let offset; + + if (index > 0 && !this._orientation.rtl) { + // If we have more accurate measurements for the cells that have been + // measured already, we should use them. 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 highestMeasuredCellIndex = this.getHighestMeasuredCellIndex(); + 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, }; diff --git a/packages/virtualized-lists/Lists/__tests__/ListMetricsAggregator-test.js b/packages/virtualized-lists/Lists/__tests__/ListMetricsAggregator-test.js index 2278ca9e50cbff..4a08575c991745 100644 --- a/packages/virtualized-lists/Lists/__tests__/ListMetricsAggregator-test.js +++ b/packages/virtualized-lists/Lists/__tests__/ListMetricsAggregator-test.js @@ -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, @@ -528,7 +528,7 @@ describe('ListMetricsAggregator', () => { 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,