Skip to content

Commit

Permalink
Support platform customization of inverted VirtualizedList (#32038)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #32038

This change slightly modifies VirtualizedList to better support platform customization of inverted behavior. For example, on react-native-windows, the transform approach to inverted breaks some scrolling input behaviors (see https://github.com/microsoft/react-native-windows/issue/4098).

In react-native-windows, inverted behavior can be implemented using a combination of Yoga and platform native anchoring behaviors. When implemented this way, some assumptions in VirtualizedList are broken, namely that layouts of list items have monotonically increasing offsets on the scroll axis.

Rather than entirely overhauling VirtualizedList to eliminate this assumption, a more trivial workaround is to invert the scroll and list item layout metrics in JS so they appear to be monotonically increasing for features like data loading and onEndReached events, and using raw metrics for features like scrollToIndex.

We can accomplish most of this by overwriting the `_getScrollMetrics()` and `_getFrameMetrics()` functions in the platform VirtualizedList JS module.

## Changelog:
[General][Added] - Support platform customization of inverted VirtualizedList

Differential Revision: D30398027

fbshipit-source-id: 8ab3c2bdec29b7d9347fea1b89d034d1e7328393
  • Loading branch information
rozele authored and facebook-github-bot committed Aug 18, 2021
1 parent 06e31c7 commit 9b0bc84
Showing 1 changed file with 16 additions and 9 deletions.
25 changes: 16 additions & 9 deletions Libraries/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,10 @@ class VirtualizedList extends React.PureComponent<Props, State> {
scrollToEnd(params?: ?{animated?: ?boolean, ...}) {
const animated = params ? params.animated : true;
const veryLast = this.props.getItemCount(this.props.data) - 1;
const frame = this._getFrameMetricsApprox(veryLast);
const frame = this._getFrameMetricsApprox(
veryLast,
/*useRawMetrics*/ true,
);
const offset = Math.max(
0,
frame.offset +
Expand Down Expand Up @@ -458,7 +461,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
});
return;
}
const frame = this._getFrameMetricsApprox(index);
const frame = this._getFrameMetricsApprox(index, /*useRawMetrics*/ true);
const offset =
Math.max(
0,
Expand Down Expand Up @@ -1073,6 +1076,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
const scrollProps = {
...this.props,
contentContainerStyle: contentInversionStyle
? [contentInversionStyle, this.props.contentContainerStyle]
: this.props.contentContainerStyle,
onContentSizeChange: this._onContentSizeChange,
onLayout: this._onLayout,
onScroll: this._onScroll,
Expand Down Expand Up @@ -1230,7 +1236,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
this._fillRateHelper.computeBlankness(
this.props,
this.state,
this._scrollMetrics,
this._getScrollMetrics(),
);
}

Expand Down Expand Up @@ -1502,7 +1508,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
onEndReached,
onEndReachedThreshold,
} = this.props;
const {contentLength, visibleLength, offset} = this._scrollMetrics;
const {contentLength, visibleLength, offset} = this._getScrollMetrics();
const distanceFromEnd = contentLength - visibleLength - offset;
const threshold =
onEndReachedThreshold != null ? onEndReachedThreshold * visibleLength : 2;
Expand Down Expand Up @@ -1641,7 +1647,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {

_scheduleCellsToRenderUpdate() {
const {first, last} = this.state;
const {offset, visibleLength, velocity} = this._scrollMetrics;
const {offset, visibleLength, velocity} = this._getScrollMetrics();
const itemCount = this.props.getItemCount(this.props.data);
let hiPri = false;
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
Expand Down Expand Up @@ -1743,7 +1749,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
this.setState(state => {
let newState;
const {contentLength, offset, visibleLength} = this._scrollMetrics;
const {contentLength, offset, visibleLength} = this._getScrollMetrics();
if (!isVirtualizationDisabled) {
// If we run this with bogus data, we'll force-render window {first: 0, last: 0},
// and wipe out the initialNumToRender rendered elements.
Expand All @@ -1754,15 +1760,15 @@ class VirtualizedList extends React.PureComponent<Props, State> {
// we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex.
// So let's wait until we've scrolled the view to the right place. And until then,
// we will trust the initialScrollIndex suggestion.
if (!this.props.initialScrollIndex || this._scrollMetrics.offset) {
if (!this.props.initialScrollIndex || offset) {
newState = computeWindowedRenderLimits(
this.props.data,
this.props.getItemCount,
maxToRenderPerBatchOrDefault(this.props.maxToRenderPerBatch),
windowSizeOrDefault(this.props.windowSize),
state,
this._getFrameMetricsApprox,
this._scrollMetrics,
this._getScrollMetrics(),
);
}
}
Expand Down Expand Up @@ -1827,6 +1833,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {

_getFrameMetricsApprox = (
index: number,
useRawMetrics?: boolean,
): {
length: number,
offset: number,
Expand Down Expand Up @@ -1882,7 +1889,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
this._viewabilityTuples.forEach(tuple => {
tuple.viewabilityHelper.onUpdate(
getItemCount(data),
this._scrollMetrics.offset,
this._getScrollMetrics().offset,
this._scrollMetrics.visibleLength,
this._getFrameMetrics,
this._createViewToken,
Expand Down

0 comments on commit 9b0bc84

Please sign in to comment.