From 40aaeb71814a1987482d97fd3170af0add55bc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Fri, 13 Sep 2024 16:18:33 -0700 Subject: [PATCH] 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 --- .../Lists/ListMetricsAggregator.js | 26 ++++++++++++++++++- .../__tests__/ListMetricsAggregator-test.js | 17 ++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/virtualized-lists/Lists/ListMetricsAggregator.js b/packages/virtualized-lists/Lists/ListMetricsAggregator.js index a7972390211..9516269aee0 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; + + 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, }; diff --git a/packages/virtualized-lists/Lists/__tests__/ListMetricsAggregator-test.js b/packages/virtualized-lists/Lists/__tests__/ListMetricsAggregator-test.js index 2278ca9e50c..7b5e416d419 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, @@ -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,