diff --git a/packages/virtualized-lists/Lists/ListMetricsAggregator.js b/packages/virtualized-lists/Lists/ListMetricsAggregator.js index a5203ae93fcc61..23f7f8d327ca16 100644 --- a/packages/virtualized-lists/Lists/ListMetricsAggregator.js +++ b/packages/virtualized-lists/Lists/ListMetricsAggregator.js @@ -14,6 +14,8 @@ import {keyExtractor as defaultKeyExtractor} from './VirtualizeUtils'; import invariant from 'invariant'; +type LayoutEventDirection = 'top-down' | 'bottom-up'; + export type CellMetrics = { /** * Index of the item in the list @@ -83,7 +85,7 @@ export default class ListMetricsAggregator { // Fabric and Paper may call onLayout in different orders. We can tell which // direction layout events happen on the first layout. - _onLayoutDirection: 'top-down' | 'bottom-up' = 'top-down'; + _onLayoutDirection: LayoutEventDirection = 'top-down'; /** * Notify the ListMetricsAggregator that a cell has been laid out. @@ -284,6 +286,26 @@ export default class ListMetricsAggregator { return this._contentLength != null; } + /** + * Whether the ListMetricsAggregator is notified of cell metrics before + * ScrollView metrics (bottom-up) or ScrollView metrics before cell metrics + * (top-down). + * + * Must be queried after cell layout + */ + getLayoutEventDirection(): LayoutEventDirection { + return this._onLayoutDirection; + } + + /** + * Whether the ListMetricsAggregator must be aware of the current length of + * ScrollView content to be able to correctly resolve the (flow-relative) + * metrics of a cell. + */ + needsContentLengthForCellMetrics(): boolean { + return this._orientation.horizontal && this._orientation.rtl; + } + /** * Finds the flow-relative offset (e.g. starting from the left in LTR, but * right in RTL) from a layout box. diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index 0306925242d145..fed3f3f140b9a4 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -1211,6 +1211,7 @@ class VirtualizedList extends StateSafePureComponent { _nestedChildLists: ChildListCollection = new ChildListCollection(); _offsetFromParentVirtualizedList: number = 0; + _pendingViewabilityUpdate: boolean = false; _prevParentOffset: number = 0; _scrollMetrics: { dOffset: number, @@ -1301,21 +1302,39 @@ class VirtualizedList extends StateSafePureComponent { orientation: this._orientation(), }); + // In RTL layout we need parent content length to calculate the offset of a + // cell from the start of the list. In Paper, layout events are bottom up, + // so we do not know this yet, and must defer calculation until after + // `onContentSizeChange` is called. + const deferCellMetricCalculation = + this._listMetrics.getLayoutEventDirection() === 'bottom-up' && + this._listMetrics.needsContentLengthForCellMetrics(); + + // Note: In Paper RTL logical position may have changed when + // `layoutHasChanged` is false if a cell maintains same X/Y coordinates, + // but contentLength shifts. This will be corrected by + // `onContentSizeChange` triggering a cell update. if (layoutHasChanged) { - // TODO: We have not yet received parent content length, meaning we do not - // yet have up to date offsets in RTL. This means layout queries done - // when scheduling a new batch may not yet be correct. This is corrected - // when we schedule again in response to `onContentSizeChange`. - const {horizontal, rtl} = this._orientation(); this._scheduleCellsToRenderUpdate({ - allowImmediateExecution: !(horizontal && rtl), + allowImmediateExecution: !deferCellMetricCalculation, }); } this._triggerRemeasureForChildListsInCell(cellKey); this._computeBlankness(); - this._updateViewableItems(this.props, this.state.cellsAroundViewport); + + if (deferCellMetricCalculation) { + if (!this._pendingViewabilityUpdate) { + this._pendingViewabilityUpdate = true; + setTimeout(() => { + this._updateViewableItems(this.props, this.state.cellsAroundViewport); + this._pendingViewabilityUpdate = false; + }, 0); + } + } else { + this._updateViewableItems(this.props, this.state.cellsAroundViewport); + } }; _onCellFocusCapture(cellKey: string) {