@react-native/virtualized-lists 0.73.0 → 0.73.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Lists/FillRateHelper.js +12 -20
- package/Lists/ListMetricsAggregator.js +385 -0
- package/Lists/StateSafePureComponent.js +2 -2
- package/Lists/ViewabilityHelper.js +15 -27
- package/Lists/VirtualizeUtils.js +11 -24
- package/Lists/VirtualizedList.d.ts +1 -1
- package/Lists/VirtualizedList.js +390 -337
- package/Lists/VirtualizedListCellRenderer.js +1 -1
- package/Lists/VirtualizedListContext.js +1 -3
- package/Lists/VirtualizedListProps.js +40 -9
- package/Lists/VirtualizedSectionList.js +5 -5
- package/README.md +21 -0
- package/package.json +15 -5
package/Lists/VirtualizedList.js
CHANGED
|
@@ -16,15 +16,17 @@ import type {
|
|
|
16
16
|
} from 'react-native/Libraries/Types/CoreEventTypes';
|
|
17
17
|
import type {ViewToken} from './ViewabilityHelper';
|
|
18
18
|
import type {
|
|
19
|
-
FrameMetricProps,
|
|
20
19
|
Item,
|
|
21
20
|
Props,
|
|
22
21
|
RenderItemProps,
|
|
23
22
|
RenderItemType,
|
|
24
23
|
Separators,
|
|
25
24
|
} from './VirtualizedListProps';
|
|
25
|
+
import type {CellMetricProps, ListOrientation} from './ListMetricsAggregator';
|
|
26
26
|
|
|
27
27
|
import {
|
|
28
|
+
I18nManager,
|
|
29
|
+
Platform,
|
|
28
30
|
RefreshControl,
|
|
29
31
|
ScrollView,
|
|
30
32
|
View,
|
|
@@ -37,6 +39,7 @@ import infoLog from '../Utilities/infoLog';
|
|
|
37
39
|
import {CellRenderMask} from './CellRenderMask';
|
|
38
40
|
import ChildListCollection from './ChildListCollection';
|
|
39
41
|
import FillRateHelper from './FillRateHelper';
|
|
42
|
+
import ListMetricsAggregator from './ListMetricsAggregator';
|
|
40
43
|
import StateSafePureComponent from './StateSafePureComponent';
|
|
41
44
|
import ViewabilityHelper from './ViewabilityHelper';
|
|
42
45
|
import CellRenderer from './VirtualizedListCellRenderer';
|
|
@@ -53,6 +56,15 @@ import invariant from 'invariant';
|
|
|
53
56
|
import nullthrows from 'nullthrows';
|
|
54
57
|
import * as React from 'react';
|
|
55
58
|
|
|
59
|
+
import {
|
|
60
|
+
horizontalOrDefault,
|
|
61
|
+
initialNumToRenderOrDefault,
|
|
62
|
+
maxToRenderPerBatchOrDefault,
|
|
63
|
+
onStartReachedThresholdOrDefault,
|
|
64
|
+
onEndReachedThresholdOrDefault,
|
|
65
|
+
windowSizeOrDefault,
|
|
66
|
+
} from './VirtualizedListProps';
|
|
67
|
+
|
|
56
68
|
export type {RenderItemProps, RenderItemType, Separators};
|
|
57
69
|
|
|
58
70
|
const ON_EDGE_REACHED_EPSILON = 0.001;
|
|
@@ -73,53 +85,12 @@ type ViewabilityHelperCallbackTuple = {
|
|
|
73
85
|
type State = {
|
|
74
86
|
renderMask: CellRenderMask,
|
|
75
87
|
cellsAroundViewport: {first: number, last: number},
|
|
88
|
+
// Used to track items added at the start of the list for maintainVisibleContentPosition.
|
|
89
|
+
firstVisibleItemKey: ?string,
|
|
90
|
+
// When > 0 the scroll position available in JS is considered stale and should not be used.
|
|
91
|
+
pendingScrollUpdateCount: number,
|
|
76
92
|
};
|
|
77
93
|
|
|
78
|
-
/**
|
|
79
|
-
* Default Props Helper Functions
|
|
80
|
-
* Use the following helper functions for default values
|
|
81
|
-
*/
|
|
82
|
-
|
|
83
|
-
// horizontalOrDefault(this.props.horizontal)
|
|
84
|
-
function horizontalOrDefault(horizontal: ?boolean) {
|
|
85
|
-
return horizontal ?? false;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// initialNumToRenderOrDefault(this.props.initialNumToRender)
|
|
89
|
-
function initialNumToRenderOrDefault(initialNumToRender: ?number) {
|
|
90
|
-
return initialNumToRender ?? 10;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// maxToRenderPerBatchOrDefault(this.props.maxToRenderPerBatch)
|
|
94
|
-
function maxToRenderPerBatchOrDefault(maxToRenderPerBatch: ?number) {
|
|
95
|
-
return maxToRenderPerBatch ?? 10;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// onStartReachedThresholdOrDefault(this.props.onStartReachedThreshold)
|
|
99
|
-
function onStartReachedThresholdOrDefault(onStartReachedThreshold: ?number) {
|
|
100
|
-
return onStartReachedThreshold ?? 2;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// onEndReachedThresholdOrDefault(this.props.onEndReachedThreshold)
|
|
104
|
-
function onEndReachedThresholdOrDefault(onEndReachedThreshold: ?number) {
|
|
105
|
-
return onEndReachedThreshold ?? 2;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// getScrollingThreshold(visibleLength, onEndReachedThreshold)
|
|
109
|
-
function getScrollingThreshold(threshold: number, visibleLength: number) {
|
|
110
|
-
return (threshold * visibleLength) / 2;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// scrollEventThrottleOrDefault(this.props.scrollEventThrottle)
|
|
114
|
-
function scrollEventThrottleOrDefault(scrollEventThrottle: ?number) {
|
|
115
|
-
return scrollEventThrottle ?? 50;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// windowSizeOrDefault(this.props.windowSize)
|
|
119
|
-
function windowSizeOrDefault(windowSize: ?number) {
|
|
120
|
-
return windowSize ?? 21;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
94
|
function findLastWhere<T>(
|
|
124
95
|
arr: $ReadOnlyArray<T>,
|
|
125
96
|
predicate: (element: T) => boolean,
|
|
@@ -133,6 +104,10 @@ function findLastWhere<T>(
|
|
|
133
104
|
return null;
|
|
134
105
|
}
|
|
135
106
|
|
|
107
|
+
function getScrollingThreshold(threshold: number, visibleLength: number) {
|
|
108
|
+
return (threshold * visibleLength) / 2;
|
|
109
|
+
}
|
|
110
|
+
|
|
136
111
|
/**
|
|
137
112
|
* Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist)
|
|
138
113
|
* and [`<SectionList>`](https://reactnative.dev/docs/sectionlist) components, which are also better
|
|
@@ -172,7 +147,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
172
147
|
if (veryLast < 0) {
|
|
173
148
|
return;
|
|
174
149
|
}
|
|
175
|
-
const frame = this.
|
|
150
|
+
const frame = this._listMetrics.getCellMetricsApprox(veryLast, this.props);
|
|
176
151
|
const offset = Math.max(
|
|
177
152
|
0,
|
|
178
153
|
frame.offset +
|
|
@@ -181,24 +156,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
181
156
|
this._scrollMetrics.visibleLength,
|
|
182
157
|
);
|
|
183
158
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (this._scrollRef.scrollTo == null) {
|
|
189
|
-
console.warn(
|
|
190
|
-
'No scrollTo method provided. This may be because you have two nested ' +
|
|
191
|
-
'VirtualizedLists with the same orientation, or because you are ' +
|
|
192
|
-
'using a custom component that does not implement scrollTo.',
|
|
193
|
-
);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
this._scrollRef.scrollTo(
|
|
198
|
-
horizontalOrDefault(this.props.horizontal)
|
|
199
|
-
? {x: offset, animated}
|
|
200
|
-
: {y: offset, animated},
|
|
201
|
-
);
|
|
159
|
+
// TODO: consider using `ref.scrollToEnd` directly
|
|
160
|
+
this.scrollToOffset({animated, offset});
|
|
202
161
|
}
|
|
203
162
|
|
|
204
163
|
// scrollToIndex may be janky without getItemLayout prop
|
|
@@ -209,13 +168,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
209
168
|
viewPosition?: number,
|
|
210
169
|
...
|
|
211
170
|
}): $FlowFixMe {
|
|
212
|
-
const {
|
|
213
|
-
|
|
214
|
-
horizontal,
|
|
215
|
-
getItemCount,
|
|
216
|
-
getItemLayout,
|
|
217
|
-
onScrollToIndexFailed,
|
|
218
|
-
} = this.props;
|
|
171
|
+
const {data, getItemCount, getItemLayout, onScrollToIndexFailed} =
|
|
172
|
+
this.props;
|
|
219
173
|
const {animated, index, viewOffset, viewPosition} = params;
|
|
220
174
|
invariant(
|
|
221
175
|
index >= 0,
|
|
@@ -233,44 +187,36 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
233
187
|
getItemCount(data) - 1
|
|
234
188
|
}`,
|
|
235
189
|
);
|
|
236
|
-
if (
|
|
190
|
+
if (
|
|
191
|
+
!getItemLayout &&
|
|
192
|
+
index > this._listMetrics.getHighestMeasuredCellIndex()
|
|
193
|
+
) {
|
|
237
194
|
invariant(
|
|
238
195
|
!!onScrollToIndexFailed,
|
|
239
196
|
'scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, ' +
|
|
240
197
|
'otherwise there is no way to know the location of offscreen indices or handle failures.',
|
|
241
198
|
);
|
|
242
199
|
onScrollToIndexFailed({
|
|
243
|
-
averageItemLength: this.
|
|
244
|
-
highestMeasuredFrameIndex:
|
|
200
|
+
averageItemLength: this._listMetrics.getAverageCellLength(),
|
|
201
|
+
highestMeasuredFrameIndex:
|
|
202
|
+
this._listMetrics.getHighestMeasuredCellIndex(),
|
|
245
203
|
index,
|
|
246
204
|
});
|
|
247
205
|
return;
|
|
248
206
|
}
|
|
249
|
-
const frame = this.
|
|
207
|
+
const frame = this._listMetrics.getCellMetricsApprox(
|
|
208
|
+
Math.floor(index),
|
|
209
|
+
this.props,
|
|
210
|
+
);
|
|
250
211
|
const offset =
|
|
251
212
|
Math.max(
|
|
252
213
|
0,
|
|
253
|
-
this.
|
|
214
|
+
this._listMetrics.getCellOffsetApprox(index, this.props) -
|
|
254
215
|
(viewPosition || 0) *
|
|
255
216
|
(this._scrollMetrics.visibleLength - frame.length),
|
|
256
217
|
) - (viewOffset || 0);
|
|
257
218
|
|
|
258
|
-
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (this._scrollRef.scrollTo == null) {
|
|
263
|
-
console.warn(
|
|
264
|
-
'No scrollTo method provided. This may be because you have two nested ' +
|
|
265
|
-
'VirtualizedLists with the same orientation, or because you are ' +
|
|
266
|
-
'using a custom component that does not implement scrollTo.',
|
|
267
|
-
);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
this._scrollRef.scrollTo(
|
|
272
|
-
horizontal ? {x: offset, animated} : {y: offset, animated},
|
|
273
|
-
);
|
|
219
|
+
this.scrollToOffset({offset, animated});
|
|
274
220
|
}
|
|
275
221
|
|
|
276
222
|
// scrollToItem may be janky without getItemLayout prop. Required linear scan through items -
|
|
@@ -305,12 +251,13 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
305
251
|
*/
|
|
306
252
|
scrollToOffset(params: {animated?: ?boolean, offset: number, ...}) {
|
|
307
253
|
const {animated, offset} = params;
|
|
254
|
+
const scrollRef = this._scrollRef;
|
|
308
255
|
|
|
309
|
-
if (
|
|
256
|
+
if (scrollRef == null) {
|
|
310
257
|
return;
|
|
311
258
|
}
|
|
312
259
|
|
|
313
|
-
if (
|
|
260
|
+
if (scrollRef.scrollTo == null) {
|
|
314
261
|
console.warn(
|
|
315
262
|
'No scrollTo method provided. This may be because you have two nested ' +
|
|
316
263
|
'VirtualizedLists with the same orientation, or because you are ' +
|
|
@@ -319,11 +266,31 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
319
266
|
return;
|
|
320
267
|
}
|
|
321
268
|
|
|
322
|
-
this.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
269
|
+
const {horizontal, rtl} = this._orientation();
|
|
270
|
+
if (horizontal && rtl && !this._listMetrics.hasContentLength()) {
|
|
271
|
+
console.warn(
|
|
272
|
+
'scrollToOffset may not be called in RTL before content is laid out',
|
|
273
|
+
);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
scrollRef.scrollTo({
|
|
278
|
+
animated,
|
|
279
|
+
...this._scrollToParamsFromOffset(offset),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_scrollToParamsFromOffset(offset: number): {x?: number, y?: number} {
|
|
284
|
+
const {horizontal, rtl} = this._orientation();
|
|
285
|
+
if (horizontal && rtl) {
|
|
286
|
+
// Add the visible length of the scrollview so that the offset is right-aligned
|
|
287
|
+
const cartOffset = this._listMetrics.cartesianOffset(
|
|
288
|
+
offset + this._scrollMetrics.visibleLength,
|
|
289
|
+
);
|
|
290
|
+
return horizontal ? {x: cartOffset} : {y: cartOffset};
|
|
291
|
+
} else {
|
|
292
|
+
return horizontal ? {x: offset} : {y: offset};
|
|
293
|
+
}
|
|
327
294
|
}
|
|
328
295
|
|
|
329
296
|
recordInteraction() {
|
|
@@ -423,7 +390,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
423
390
|
super(props);
|
|
424
391
|
this._checkProps(props);
|
|
425
392
|
|
|
426
|
-
this._fillRateHelper = new FillRateHelper(this.
|
|
393
|
+
this._fillRateHelper = new FillRateHelper(this._listMetrics);
|
|
427
394
|
this._updateCellsToRenderBatcher = new Batchinator(
|
|
428
395
|
this._updateCellsToRender,
|
|
429
396
|
this.props.updateCellsBatchingPeriod ?? 50,
|
|
@@ -448,9 +415,24 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
448
415
|
|
|
449
416
|
const initialRenderRegion = VirtualizedList._initialRenderRegion(props);
|
|
450
417
|
|
|
418
|
+
const minIndexForVisible =
|
|
419
|
+
this.props.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
|
|
420
|
+
|
|
451
421
|
this.state = {
|
|
452
422
|
cellsAroundViewport: initialRenderRegion,
|
|
453
423
|
renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion),
|
|
424
|
+
firstVisibleItemKey:
|
|
425
|
+
this.props.getItemCount(this.props.data) > minIndexForVisible
|
|
426
|
+
? VirtualizedList._getItemKey(this.props, minIndexForVisible)
|
|
427
|
+
: null,
|
|
428
|
+
// When we have a non-zero initialScrollIndex, we will receive a
|
|
429
|
+
// scroll event later so this will prevent the window from updating
|
|
430
|
+
// until we get a valid offset.
|
|
431
|
+
pendingScrollUpdateCount:
|
|
432
|
+
this.props.initialScrollIndex != null &&
|
|
433
|
+
this.props.initialScrollIndex > 0
|
|
434
|
+
? 1
|
|
435
|
+
: 0,
|
|
454
436
|
};
|
|
455
437
|
}
|
|
456
438
|
|
|
@@ -502,6 +484,40 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
502
484
|
}
|
|
503
485
|
}
|
|
504
486
|
|
|
487
|
+
static _findItemIndexWithKey(
|
|
488
|
+
props: Props,
|
|
489
|
+
key: string,
|
|
490
|
+
hint: ?number,
|
|
491
|
+
): ?number {
|
|
492
|
+
const itemCount = props.getItemCount(props.data);
|
|
493
|
+
if (hint != null && hint >= 0 && hint < itemCount) {
|
|
494
|
+
const curKey = VirtualizedList._getItemKey(props, hint);
|
|
495
|
+
if (curKey === key) {
|
|
496
|
+
return hint;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
for (let ii = 0; ii < itemCount; ii++) {
|
|
500
|
+
const curKey = VirtualizedList._getItemKey(props, ii);
|
|
501
|
+
if (curKey === key) {
|
|
502
|
+
return ii;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
static _getItemKey(
|
|
509
|
+
props: {
|
|
510
|
+
data: Props['data'],
|
|
511
|
+
getItem: Props['getItem'],
|
|
512
|
+
keyExtractor: Props['keyExtractor'],
|
|
513
|
+
...
|
|
514
|
+
},
|
|
515
|
+
index: number,
|
|
516
|
+
): string {
|
|
517
|
+
const item = props.getItem(props.data, index);
|
|
518
|
+
return VirtualizedList._keyExtractor(item, index, props);
|
|
519
|
+
}
|
|
520
|
+
|
|
505
521
|
static _createRenderMask(
|
|
506
522
|
props: Props,
|
|
507
523
|
cellsAroundViewport: {first: number, last: number},
|
|
@@ -585,12 +601,14 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
585
601
|
_adjustCellsAroundViewport(
|
|
586
602
|
props: Props,
|
|
587
603
|
cellsAroundViewport: {first: number, last: number},
|
|
604
|
+
pendingScrollUpdateCount: number,
|
|
588
605
|
): {first: number, last: number} {
|
|
589
606
|
const {data, getItemCount} = props;
|
|
590
607
|
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
|
|
591
608
|
props.onEndReachedThreshold,
|
|
592
609
|
);
|
|
593
|
-
const {
|
|
610
|
+
const {offset, visibleLength} = this._scrollMetrics;
|
|
611
|
+
const contentLength = this._listMetrics.getContentLength();
|
|
594
612
|
const distanceFromEnd = contentLength - visibleLength - offset;
|
|
595
613
|
|
|
596
614
|
// Wait until the scroll view metrics have been set up. And until then,
|
|
@@ -616,21 +634,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
616
634
|
),
|
|
617
635
|
};
|
|
618
636
|
} else {
|
|
619
|
-
// If we have a
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
// we will trust the initialScrollIndex suggestion.
|
|
623
|
-
|
|
624
|
-
// Thus, we want to recalculate the windowed render limits if any of the following hold:
|
|
625
|
-
// - initialScrollIndex is undefined or is 0
|
|
626
|
-
// - initialScrollIndex > 0 AND scrolling is complete
|
|
627
|
-
// - initialScrollIndex > 0 AND the end of the list is visible (this handles the case
|
|
628
|
-
// where the list is shorter than the visible area)
|
|
629
|
-
if (
|
|
630
|
-
props.initialScrollIndex &&
|
|
631
|
-
!this._scrollMetrics.offset &&
|
|
632
|
-
Math.abs(distanceFromEnd) >= Number.EPSILON
|
|
633
|
-
) {
|
|
637
|
+
// If we have a pending scroll update, we should not adjust the render window as it
|
|
638
|
+
// might override the correct window.
|
|
639
|
+
if (pendingScrollUpdateCount > 0) {
|
|
634
640
|
return cellsAroundViewport.last >= getItemCount(data)
|
|
635
641
|
? VirtualizedList._constrainToItemCount(cellsAroundViewport, props)
|
|
636
642
|
: cellsAroundViewport;
|
|
@@ -641,7 +647,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
641
647
|
maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch),
|
|
642
648
|
windowSizeOrDefault(props.windowSize),
|
|
643
649
|
cellsAroundViewport,
|
|
644
|
-
this.
|
|
650
|
+
this._listMetrics,
|
|
645
651
|
this._scrollMetrics,
|
|
646
652
|
);
|
|
647
653
|
invariant(
|
|
@@ -712,14 +718,59 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
712
718
|
return prevState;
|
|
713
719
|
}
|
|
714
720
|
|
|
721
|
+
let maintainVisibleContentPositionAdjustment: ?number = null;
|
|
722
|
+
const prevFirstVisibleItemKey = prevState.firstVisibleItemKey;
|
|
723
|
+
const minIndexForVisible =
|
|
724
|
+
newProps.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
|
|
725
|
+
const newFirstVisibleItemKey =
|
|
726
|
+
newProps.getItemCount(newProps.data) > minIndexForVisible
|
|
727
|
+
? VirtualizedList._getItemKey(newProps, minIndexForVisible)
|
|
728
|
+
: null;
|
|
729
|
+
if (
|
|
730
|
+
newProps.maintainVisibleContentPosition != null &&
|
|
731
|
+
prevFirstVisibleItemKey != null &&
|
|
732
|
+
newFirstVisibleItemKey != null
|
|
733
|
+
) {
|
|
734
|
+
if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) {
|
|
735
|
+
// Fast path if items were added at the start of the list.
|
|
736
|
+
const hint =
|
|
737
|
+
itemCount - prevState.renderMask.numCells() + minIndexForVisible;
|
|
738
|
+
const firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey(
|
|
739
|
+
newProps,
|
|
740
|
+
prevFirstVisibleItemKey,
|
|
741
|
+
hint,
|
|
742
|
+
);
|
|
743
|
+
maintainVisibleContentPositionAdjustment =
|
|
744
|
+
firstVisibleItemIndex != null
|
|
745
|
+
? firstVisibleItemIndex - minIndexForVisible
|
|
746
|
+
: null;
|
|
747
|
+
} else {
|
|
748
|
+
maintainVisibleContentPositionAdjustment = null;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
715
752
|
const constrainedCells = VirtualizedList._constrainToItemCount(
|
|
716
|
-
|
|
753
|
+
maintainVisibleContentPositionAdjustment != null
|
|
754
|
+
? {
|
|
755
|
+
first:
|
|
756
|
+
prevState.cellsAroundViewport.first +
|
|
757
|
+
maintainVisibleContentPositionAdjustment,
|
|
758
|
+
last:
|
|
759
|
+
prevState.cellsAroundViewport.last +
|
|
760
|
+
maintainVisibleContentPositionAdjustment,
|
|
761
|
+
}
|
|
762
|
+
: prevState.cellsAroundViewport,
|
|
717
763
|
newProps,
|
|
718
764
|
);
|
|
719
765
|
|
|
720
766
|
return {
|
|
721
767
|
cellsAroundViewport: constrainedCells,
|
|
722
768
|
renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells),
|
|
769
|
+
firstVisibleItemKey: newFirstVisibleItemKey,
|
|
770
|
+
pendingScrollUpdateCount:
|
|
771
|
+
maintainVisibleContentPositionAdjustment != null
|
|
772
|
+
? prevState.pendingScrollUpdateCount + 1
|
|
773
|
+
: prevState.pendingScrollUpdateCount,
|
|
723
774
|
};
|
|
724
775
|
}
|
|
725
776
|
|
|
@@ -751,7 +802,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
751
802
|
|
|
752
803
|
for (let ii = first; ii <= last; ii++) {
|
|
753
804
|
const item = getItem(data, ii);
|
|
754
|
-
const key =
|
|
805
|
+
const key = VirtualizedList._keyExtractor(item, ii, this.props);
|
|
755
806
|
|
|
756
807
|
this._indicesToKeys.set(ii, key);
|
|
757
808
|
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
|
|
@@ -794,15 +845,19 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
794
845
|
props: Props,
|
|
795
846
|
): {first: number, last: number} {
|
|
796
847
|
const itemCount = props.getItemCount(props.data);
|
|
797
|
-
const
|
|
848
|
+
const lastPossibleCellIndex = itemCount - 1;
|
|
798
849
|
|
|
850
|
+
// Constraining `last` may significantly shrink the window. Adjust `first`
|
|
851
|
+
// to expand the window if the new `last` results in a new window smaller
|
|
852
|
+
// than the number of cells rendered per batch.
|
|
799
853
|
const maxToRenderPerBatch = maxToRenderPerBatchOrDefault(
|
|
800
854
|
props.maxToRenderPerBatch,
|
|
801
855
|
);
|
|
856
|
+
const maxFirst = Math.max(0, lastPossibleCellIndex - maxToRenderPerBatch);
|
|
802
857
|
|
|
803
858
|
return {
|
|
804
|
-
first: clamp(0,
|
|
805
|
-
last,
|
|
859
|
+
first: clamp(0, cells.first, maxFirst),
|
|
860
|
+
last: Math.min(lastPossibleCellIndex, cells.last),
|
|
806
861
|
};
|
|
807
862
|
}
|
|
808
863
|
|
|
@@ -824,15 +879,14 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
824
879
|
_getSpacerKey = (isVertical: boolean): string =>
|
|
825
880
|
isVertical ? 'height' : 'width';
|
|
826
881
|
|
|
827
|
-
_keyExtractor(
|
|
882
|
+
static _keyExtractor(
|
|
828
883
|
item: Item,
|
|
829
884
|
index: number,
|
|
830
885
|
props: {
|
|
831
886
|
keyExtractor?: ?(item: Item, index: number) => string,
|
|
832
887
|
...
|
|
833
888
|
},
|
|
834
|
-
|
|
835
|
-
) {
|
|
889
|
+
): string {
|
|
836
890
|
if (props.keyExtractor != null) {
|
|
837
891
|
return props.keyExtractor(item, index);
|
|
838
892
|
}
|
|
@@ -878,6 +932,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
878
932
|
cellKey={this._getCellKey() + '-header'}
|
|
879
933
|
key="$header">
|
|
880
934
|
<View
|
|
935
|
+
// We expect that header component will be a single native view so make it
|
|
936
|
+
// not collapsable to avoid this view being flattened and make this assumption
|
|
937
|
+
// no longer true.
|
|
938
|
+
collapsable={false}
|
|
881
939
|
onLayout={this._onLayoutHeader}
|
|
882
940
|
style={StyleSheet.compose(
|
|
883
941
|
inversionStyle,
|
|
@@ -947,15 +1005,18 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
947
1005
|
? clamp(
|
|
948
1006
|
section.first - 1,
|
|
949
1007
|
section.last,
|
|
950
|
-
this.
|
|
1008
|
+
this._listMetrics.getHighestMeasuredCellIndex(),
|
|
951
1009
|
)
|
|
952
1010
|
: section.last;
|
|
953
1011
|
|
|
954
|
-
const firstMetrics = this.
|
|
1012
|
+
const firstMetrics = this._listMetrics.getCellMetricsApprox(
|
|
955
1013
|
section.first,
|
|
956
1014
|
this.props,
|
|
957
1015
|
);
|
|
958
|
-
const lastMetrics = this.
|
|
1016
|
+
const lastMetrics = this._listMetrics.getCellMetricsApprox(
|
|
1017
|
+
last,
|
|
1018
|
+
this.props,
|
|
1019
|
+
);
|
|
959
1020
|
const spacerSize =
|
|
960
1021
|
lastMetrics.offset + lastMetrics.length - firstMetrics.offset;
|
|
961
1022
|
cells.push(
|
|
@@ -1024,9 +1085,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1024
1085
|
onScrollEndDrag: this._onScrollEndDrag,
|
|
1025
1086
|
onMomentumScrollBegin: this._onMomentumScrollBegin,
|
|
1026
1087
|
onMomentumScrollEnd: this._onMomentumScrollEnd,
|
|
1027
|
-
scrollEventThrottle
|
|
1028
|
-
|
|
1029
|
-
|
|
1088
|
+
// iOS/macOS requires a non-zero scrollEventThrottle to fire more than a
|
|
1089
|
+
// single notification while scrolling. This will otherwise no-op.
|
|
1090
|
+
scrollEventThrottle: this.props.scrollEventThrottle ?? 0.0001,
|
|
1030
1091
|
invertStickyHeaders:
|
|
1031
1092
|
this.props.invertStickyHeaders !== undefined
|
|
1032
1093
|
? this.props.invertStickyHeaders
|
|
@@ -1035,6 +1096,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1035
1096
|
style: inversionStyle
|
|
1036
1097
|
? [inversionStyle, this.props.style]
|
|
1037
1098
|
: this.props.style,
|
|
1099
|
+
isInvertedVirtualizedList: this.props.inverted,
|
|
1100
|
+
maintainVisibleContentPosition:
|
|
1101
|
+
this.props.maintainVisibleContentPosition != null
|
|
1102
|
+
? {
|
|
1103
|
+
...this.props.maintainVisibleContentPosition,
|
|
1104
|
+
// Adjust index to account for ListHeaderComponent.
|
|
1105
|
+
minIndexForVisible:
|
|
1106
|
+
this.props.maintainVisibleContentPosition.minIndexForVisible +
|
|
1107
|
+
(this.props.ListHeaderComponent ? 1 : 0),
|
|
1108
|
+
}
|
|
1109
|
+
: undefined,
|
|
1038
1110
|
};
|
|
1039
1111
|
|
|
1040
1112
|
this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1;
|
|
@@ -1123,17 +1195,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1123
1195
|
}
|
|
1124
1196
|
}
|
|
1125
1197
|
|
|
1126
|
-
_averageCellLength = 0;
|
|
1127
1198
|
_cellRefs: {[string]: null | CellRenderer<any>} = {};
|
|
1128
1199
|
_fillRateHelper: FillRateHelper;
|
|
1129
|
-
|
|
1130
|
-
[string]: {
|
|
1131
|
-
inLayout?: boolean,
|
|
1132
|
-
index: number,
|
|
1133
|
-
length: number,
|
|
1134
|
-
offset: number,
|
|
1135
|
-
},
|
|
1136
|
-
} = {};
|
|
1200
|
+
_listMetrics: ListMetricsAggregator = new ListMetricsAggregator();
|
|
1137
1201
|
_footerLength = 0;
|
|
1138
1202
|
// Used for preventing scrollToIndex from being called multiple times for initialScrollIndex
|
|
1139
1203
|
_hasTriggeredInitialScrollToIndex = false;
|
|
@@ -1142,16 +1206,22 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1142
1206
|
_hasWarned: {[string]: boolean} = {};
|
|
1143
1207
|
_headerLength = 0;
|
|
1144
1208
|
_hiPriInProgress: boolean = false; // flag to prevent infinite hiPri cell limit update
|
|
1145
|
-
_highestMeasuredFrameIndex = 0;
|
|
1146
1209
|
_indicesToKeys: Map<number, string> = new Map();
|
|
1147
1210
|
_lastFocusedCellKey: ?string = null;
|
|
1148
1211
|
_nestedChildLists: ChildListCollection<VirtualizedList> =
|
|
1149
1212
|
new ChildListCollection();
|
|
1150
1213
|
_offsetFromParentVirtualizedList: number = 0;
|
|
1214
|
+
_pendingViewabilityUpdate: boolean = false;
|
|
1151
1215
|
_prevParentOffset: number = 0;
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1216
|
+
_scrollMetrics: {
|
|
1217
|
+
dOffset: number,
|
|
1218
|
+
dt: number,
|
|
1219
|
+
offset: number,
|
|
1220
|
+
timestamp: number,
|
|
1221
|
+
velocity: number,
|
|
1222
|
+
visibleLength: number,
|
|
1223
|
+
zoomScale: number,
|
|
1224
|
+
} = {
|
|
1155
1225
|
dOffset: 0,
|
|
1156
1226
|
dt: 10,
|
|
1157
1227
|
offset: 0,
|
|
@@ -1163,8 +1233,6 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1163
1233
|
_scrollRef: ?React.ElementRef<any> = null;
|
|
1164
1234
|
_sentStartForContentLength = 0;
|
|
1165
1235
|
_sentEndForContentLength = 0;
|
|
1166
|
-
_totalCellLength = 0;
|
|
1167
|
-
_totalCellsMeasured = 0;
|
|
1168
1236
|
_updateCellsToRenderBatcher: Batchinator;
|
|
1169
1237
|
_viewabilityTuples: Array<ViewabilityHelperCallbackTuple> = [];
|
|
1170
1238
|
|
|
@@ -1222,39 +1290,51 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1222
1290
|
}
|
|
1223
1291
|
};
|
|
1224
1292
|
|
|
1225
|
-
_onCellLayout = (
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
this.
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1293
|
+
_onCellLayout = (
|
|
1294
|
+
e: LayoutEvent,
|
|
1295
|
+
cellKey: string,
|
|
1296
|
+
cellIndex: number,
|
|
1297
|
+
): void => {
|
|
1298
|
+
const layoutHasChanged = this._listMetrics.notifyCellLayout({
|
|
1299
|
+
cellIndex,
|
|
1300
|
+
cellKey,
|
|
1301
|
+
layout: e.nativeEvent.layout,
|
|
1302
|
+
orientation: this._orientation(),
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// In RTL layout we need parent content length to calculate the offset of a
|
|
1306
|
+
// cell from the start of the list. In Paper, layout events are bottom up,
|
|
1307
|
+
// so we do not know this yet, and must defer calculation until after
|
|
1308
|
+
// `onContentSizeChange` is called.
|
|
1309
|
+
const deferCellMetricCalculation =
|
|
1310
|
+
this._listMetrics.getLayoutEventDirection() === 'bottom-up' &&
|
|
1311
|
+
this._listMetrics.needsContentLengthForCellMetrics();
|
|
1312
|
+
|
|
1313
|
+
// Note: In Paper RTL logical position may have changed when
|
|
1314
|
+
// `layoutHasChanged` is false if a cell maintains same X/Y coordinates,
|
|
1315
|
+
// but contentLength shifts. This will be corrected by
|
|
1316
|
+
// `onContentSizeChange` triggering a cell update.
|
|
1317
|
+
if (layoutHasChanged) {
|
|
1318
|
+
this._scheduleCellsToRenderUpdate({
|
|
1319
|
+
allowImmediateExecution: !deferCellMetricCalculation,
|
|
1320
|
+
});
|
|
1252
1321
|
}
|
|
1253
1322
|
|
|
1254
1323
|
this._triggerRemeasureForChildListsInCell(cellKey);
|
|
1255
1324
|
|
|
1256
1325
|
this._computeBlankness();
|
|
1257
|
-
|
|
1326
|
+
|
|
1327
|
+
if (deferCellMetricCalculation) {
|
|
1328
|
+
if (!this._pendingViewabilityUpdate) {
|
|
1329
|
+
this._pendingViewabilityUpdate = true;
|
|
1330
|
+
setTimeout(() => {
|
|
1331
|
+
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
|
1332
|
+
this._pendingViewabilityUpdate = false;
|
|
1333
|
+
}, 0);
|
|
1334
|
+
}
|
|
1335
|
+
} else {
|
|
1336
|
+
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
|
1337
|
+
}
|
|
1258
1338
|
};
|
|
1259
1339
|
|
|
1260
1340
|
_onCellFocusCapture(cellKey: string) {
|
|
@@ -1264,10 +1344,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1264
1344
|
|
|
1265
1345
|
_onCellUnmount = (cellKey: string) => {
|
|
1266
1346
|
delete this._cellRefs[cellKey];
|
|
1267
|
-
|
|
1268
|
-
if (curr) {
|
|
1269
|
-
this._frames[cellKey] = {...curr, inLayout: false};
|
|
1270
|
-
}
|
|
1347
|
+
this._listMetrics.notifyCellUnmounted(cellKey);
|
|
1271
1348
|
};
|
|
1272
1349
|
|
|
1273
1350
|
_triggerRemeasureForChildListsInCell(cellKey: string): void {
|
|
@@ -1289,9 +1366,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1289
1366
|
this.context.getOutermostParentListRef().getScrollRef(),
|
|
1290
1367
|
(x, y, width, height) => {
|
|
1291
1368
|
this._offsetFromParentVirtualizedList = this._selectOffset({x, y});
|
|
1292
|
-
this.
|
|
1293
|
-
width,
|
|
1294
|
-
|
|
1369
|
+
this._listMetrics.notifyListContentLayout({
|
|
1370
|
+
layout: {width, height},
|
|
1371
|
+
orientation: this._orientation(),
|
|
1295
1372
|
});
|
|
1296
1373
|
const scrollMetrics = this._convertParentScrollMetrics(
|
|
1297
1374
|
this.context.getScrollMetrics(),
|
|
@@ -1363,23 +1440,20 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1363
1440
|
_renderDebugOverlay() {
|
|
1364
1441
|
const normalize =
|
|
1365
1442
|
this._scrollMetrics.visibleLength /
|
|
1366
|
-
(this.
|
|
1443
|
+
(this._listMetrics.getContentLength() || 1);
|
|
1367
1444
|
const framesInLayout = [];
|
|
1368
1445
|
const itemCount = this.props.getItemCount(this.props.data);
|
|
1369
1446
|
for (let ii = 0; ii < itemCount; ii++) {
|
|
1370
|
-
const frame = this.
|
|
1371
|
-
|
|
1372
|
-
* suppresses an error found when Flow v0.68 was deployed. To see the
|
|
1373
|
-
* error delete this comment and run Flow. */
|
|
1374
|
-
if (frame.inLayout) {
|
|
1447
|
+
const frame = this._listMetrics.getCellMetricsApprox(ii, this.props);
|
|
1448
|
+
if (frame.isMounted) {
|
|
1375
1449
|
framesInLayout.push(frame);
|
|
1376
1450
|
}
|
|
1377
1451
|
}
|
|
1378
|
-
const windowTop = this.
|
|
1452
|
+
const windowTop = this._listMetrics.getCellMetricsApprox(
|
|
1379
1453
|
this.state.cellsAroundViewport.first,
|
|
1380
1454
|
this.props,
|
|
1381
1455
|
).offset;
|
|
1382
|
-
const frameLast = this.
|
|
1456
|
+
const frameLast = this._listMetrics.getCellMetricsApprox(
|
|
1383
1457
|
this.state.cellsAroundViewport.last,
|
|
1384
1458
|
this.props,
|
|
1385
1459
|
);
|
|
@@ -1438,14 +1512,15 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1438
1512
|
: metrics.width;
|
|
1439
1513
|
}
|
|
1440
1514
|
|
|
1441
|
-
_selectOffset(
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1515
|
+
_selectOffset({x, y}: $ReadOnly<{x: number, y: number, ...}>): number {
|
|
1516
|
+
return this._orientation().horizontal ? x : y;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
_orientation(): ListOrientation {
|
|
1520
|
+
return {
|
|
1521
|
+
horizontal: horizontalOrDefault(this.props.horizontal),
|
|
1522
|
+
rtl: I18nManager.isRTL,
|
|
1523
|
+
};
|
|
1449
1524
|
}
|
|
1450
1525
|
|
|
1451
1526
|
_maybeCallOnEdgeReached() {
|
|
@@ -1456,11 +1531,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1456
1531
|
onStartReachedThreshold,
|
|
1457
1532
|
onEndReached,
|
|
1458
1533
|
onEndReachedThreshold,
|
|
1459
|
-
initialScrollIndex,
|
|
1460
1534
|
} = this.props;
|
|
1461
|
-
|
|
1535
|
+
// If we have any pending scroll updates it means that the scroll metrics
|
|
1536
|
+
// are out of date and we should not call any of the edge reached callbacks.
|
|
1537
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
const {visibleLength, offset} = this._scrollMetrics;
|
|
1462
1542
|
let distanceFromStart = offset;
|
|
1463
|
-
let distanceFromEnd =
|
|
1543
|
+
let distanceFromEnd =
|
|
1544
|
+
this._listMetrics.getContentLength() - visibleLength - offset;
|
|
1464
1545
|
|
|
1465
1546
|
// Especially when oERT is zero it's necessary to 'floor' very small distance values to be 0
|
|
1466
1547
|
// since debouncing causes us to not fire this event for every single "pixel" we scroll and can thus
|
|
@@ -1494,9 +1575,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1494
1575
|
onEndReached &&
|
|
1495
1576
|
this.state.cellsAroundViewport.last === getItemCount(data) - 1 &&
|
|
1496
1577
|
isWithinEndThreshold &&
|
|
1497
|
-
this.
|
|
1578
|
+
this._listMetrics.getContentLength() !== this._sentEndForContentLength
|
|
1498
1579
|
) {
|
|
1499
|
-
this._sentEndForContentLength = this.
|
|
1580
|
+
this._sentEndForContentLength = this._listMetrics.getContentLength();
|
|
1500
1581
|
onEndReached({distanceFromEnd});
|
|
1501
1582
|
}
|
|
1502
1583
|
|
|
@@ -1507,16 +1588,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1507
1588
|
onStartReached != null &&
|
|
1508
1589
|
this.state.cellsAroundViewport.first === 0 &&
|
|
1509
1590
|
isWithinStartThreshold &&
|
|
1510
|
-
this.
|
|
1591
|
+
this._listMetrics.getContentLength() !== this._sentStartForContentLength
|
|
1511
1592
|
) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
// timestamp to differentiate between the initial scroll metrics and when we actually
|
|
1515
|
-
// received the first scroll event.
|
|
1516
|
-
if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) {
|
|
1517
|
-
this._sentStartForContentLength = this._scrollMetrics.contentLength;
|
|
1518
|
-
onStartReached({distanceFromStart});
|
|
1519
|
-
}
|
|
1593
|
+
this._sentStartForContentLength = this._listMetrics.getContentLength();
|
|
1594
|
+
onStartReached({distanceFromStart});
|
|
1520
1595
|
}
|
|
1521
1596
|
|
|
1522
1597
|
// If the user scrolls away from the start or end and back again,
|
|
@@ -1532,9 +1607,32 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1532
1607
|
}
|
|
1533
1608
|
|
|
1534
1609
|
_onContentSizeChange = (width: number, height: number) => {
|
|
1610
|
+
this._listMetrics.notifyListContentLayout({
|
|
1611
|
+
layout: {width, height},
|
|
1612
|
+
orientation: this._orientation(),
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
this._maybeScrollToInitialScrollIndex(width, height);
|
|
1616
|
+
|
|
1617
|
+
if (this.props.onContentSizeChange) {
|
|
1618
|
+
this.props.onContentSizeChange(width, height);
|
|
1619
|
+
}
|
|
1620
|
+
this._scheduleCellsToRenderUpdate();
|
|
1621
|
+
this._maybeCallOnEdgeReached();
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
/**
|
|
1625
|
+
* Scroll to a specified `initialScrollIndex` prop after the ScrollView
|
|
1626
|
+
* content has been laid out, if it is still valid. Only a single scroll is
|
|
1627
|
+
* triggered throughout the lifetime of the list.
|
|
1628
|
+
*/
|
|
1629
|
+
_maybeScrollToInitialScrollIndex(
|
|
1630
|
+
contentWidth: number,
|
|
1631
|
+
contentHeight: number,
|
|
1632
|
+
) {
|
|
1535
1633
|
if (
|
|
1536
|
-
|
|
1537
|
-
|
|
1634
|
+
contentWidth > 0 &&
|
|
1635
|
+
contentHeight > 0 &&
|
|
1538
1636
|
this.props.initialScrollIndex != null &&
|
|
1539
1637
|
this.props.initialScrollIndex > 0 &&
|
|
1540
1638
|
!this._hasTriggeredInitialScrollToIndex
|
|
@@ -1554,13 +1652,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1554
1652
|
}
|
|
1555
1653
|
this._hasTriggeredInitialScrollToIndex = true;
|
|
1556
1654
|
}
|
|
1557
|
-
|
|
1558
|
-
this.props.onContentSizeChange(width, height);
|
|
1559
|
-
}
|
|
1560
|
-
this._scrollMetrics.contentLength = this._selectLength({height, width});
|
|
1561
|
-
this._scheduleCellsToRenderUpdate();
|
|
1562
|
-
this._maybeCallOnEdgeReached();
|
|
1563
|
-
};
|
|
1655
|
+
}
|
|
1564
1656
|
|
|
1565
1657
|
/* Translates metrics from a scroll event in a parent VirtualizedList into
|
|
1566
1658
|
* coordinates relative to the child list.
|
|
@@ -1575,7 +1667,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1575
1667
|
// Child's visible length is the same as its parent's
|
|
1576
1668
|
const visibleLength = metrics.visibleLength;
|
|
1577
1669
|
const dOffset = offset - this._scrollMetrics.offset;
|
|
1578
|
-
const contentLength = this.
|
|
1670
|
+
const contentLength = this._listMetrics.getContentLength();
|
|
1579
1671
|
|
|
1580
1672
|
return {
|
|
1581
1673
|
visibleLength,
|
|
@@ -1595,11 +1687,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1595
1687
|
const timestamp = e.timeStamp;
|
|
1596
1688
|
let visibleLength = this._selectLength(e.nativeEvent.layoutMeasurement);
|
|
1597
1689
|
let contentLength = this._selectLength(e.nativeEvent.contentSize);
|
|
1598
|
-
let offset = this.
|
|
1690
|
+
let offset = this._offsetFromScrollEvent(e);
|
|
1599
1691
|
let dOffset = offset - this._scrollMetrics.offset;
|
|
1600
1692
|
|
|
1601
1693
|
if (this._isNestedWithSameOrientation()) {
|
|
1602
|
-
if (this.
|
|
1694
|
+
if (this._listMetrics.getContentLength() === 0) {
|
|
1603
1695
|
// Ignore scroll events until onLayout has been called and we
|
|
1604
1696
|
// know our offset from our offset from our parent
|
|
1605
1697
|
return;
|
|
@@ -1634,7 +1726,6 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1634
1726
|
// For invalid negative values (w/ RTL), set this to 1.
|
|
1635
1727
|
const zoomScale = e.nativeEvent.zoomScale < 0 ? 1 : e.nativeEvent.zoomScale;
|
|
1636
1728
|
this._scrollMetrics = {
|
|
1637
|
-
contentLength,
|
|
1638
1729
|
dt,
|
|
1639
1730
|
dOffset,
|
|
1640
1731
|
offset,
|
|
@@ -1643,6 +1734,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1643
1734
|
visibleLength,
|
|
1644
1735
|
zoomScale,
|
|
1645
1736
|
};
|
|
1737
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1738
|
+
this.setState(state => ({
|
|
1739
|
+
pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1,
|
|
1740
|
+
}));
|
|
1741
|
+
}
|
|
1646
1742
|
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
|
1647
1743
|
if (!this.props) {
|
|
1648
1744
|
return;
|
|
@@ -1655,7 +1751,48 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1655
1751
|
this._scheduleCellsToRenderUpdate();
|
|
1656
1752
|
};
|
|
1657
1753
|
|
|
1658
|
-
|
|
1754
|
+
_offsetFromScrollEvent(e: ScrollEvent): number {
|
|
1755
|
+
const {contentOffset, contentSize, layoutMeasurement} = e.nativeEvent;
|
|
1756
|
+
const {horizontal, rtl} = this._orientation();
|
|
1757
|
+
if (horizontal && rtl) {
|
|
1758
|
+
return (
|
|
1759
|
+
this._selectLength(contentSize) -
|
|
1760
|
+
(this._selectOffset(contentOffset) +
|
|
1761
|
+
this._selectLength(layoutMeasurement))
|
|
1762
|
+
);
|
|
1763
|
+
} else {
|
|
1764
|
+
return this._selectOffset(contentOffset);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
_scheduleCellsToRenderUpdate(opts?: {allowImmediateExecution?: boolean}) {
|
|
1769
|
+
const allowImmediateExecution = opts?.allowImmediateExecution ?? true;
|
|
1770
|
+
|
|
1771
|
+
// Only trigger high-priority updates if we've actually rendered cells,
|
|
1772
|
+
// and with that size estimate, accurately compute how many cells we should render.
|
|
1773
|
+
// Otherwise, it would just render as many cells as it can (of zero dimension),
|
|
1774
|
+
// each time through attempting to render more (limited by maxToRenderPerBatch),
|
|
1775
|
+
// starving the renderer from actually laying out the objects and computing _averageCellLength.
|
|
1776
|
+
// If this is triggered in an `componentDidUpdate` followed by a hiPri cellToRenderUpdate
|
|
1777
|
+
// We shouldn't do another hipri cellToRenderUpdate
|
|
1778
|
+
if (
|
|
1779
|
+
allowImmediateExecution &&
|
|
1780
|
+
this._shouldRenderWithPriority() &&
|
|
1781
|
+
(this._listMetrics.getAverageCellLength() || this.props.getItemLayout) &&
|
|
1782
|
+
!this._hiPriInProgress
|
|
1783
|
+
) {
|
|
1784
|
+
this._hiPriInProgress = true;
|
|
1785
|
+
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
|
|
1786
|
+
// as possible.
|
|
1787
|
+
this._updateCellsToRenderBatcher.dispose({abort: true});
|
|
1788
|
+
this._updateCellsToRender();
|
|
1789
|
+
return;
|
|
1790
|
+
} else {
|
|
1791
|
+
this._updateCellsToRenderBatcher.schedule();
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
_shouldRenderWithPriority(): boolean {
|
|
1659
1796
|
const {first, last} = this.state.cellsAroundViewport;
|
|
1660
1797
|
const {offset, visibleLength, velocity} = this._scrollMetrics;
|
|
1661
1798
|
const itemCount = this.props.getItemCount(this.props.data);
|
|
@@ -1670,7 +1807,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1670
1807
|
// But only if there are items before the first rendered item
|
|
1671
1808
|
if (first > 0) {
|
|
1672
1809
|
const distTop =
|
|
1673
|
-
offset -
|
|
1810
|
+
offset -
|
|
1811
|
+
this._listMetrics.getCellMetricsApprox(first, this.props).offset;
|
|
1674
1812
|
hiPri =
|
|
1675
1813
|
distTop < 0 ||
|
|
1676
1814
|
(velocity < -2 &&
|
|
@@ -1681,7 +1819,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1681
1819
|
// But only if there are items after the last rendered item
|
|
1682
1820
|
if (!hiPri && last >= 0 && last < itemCount - 1) {
|
|
1683
1821
|
const distBottom =
|
|
1684
|
-
this.
|
|
1822
|
+
this._listMetrics.getCellMetricsApprox(last, this.props).offset -
|
|
1685
1823
|
(offset + visibleLength);
|
|
1686
1824
|
hiPri =
|
|
1687
1825
|
distBottom < 0 ||
|
|
@@ -1689,27 +1827,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1689
1827
|
distBottom <
|
|
1690
1828
|
getScrollingThreshold(onEndReachedThreshold, visibleLength));
|
|
1691
1829
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
// Otherwise, it would just render as many cells as it can (of zero dimension),
|
|
1695
|
-
// each time through attempting to render more (limited by maxToRenderPerBatch),
|
|
1696
|
-
// starving the renderer from actually laying out the objects and computing _averageCellLength.
|
|
1697
|
-
// If this is triggered in an `componentDidUpdate` followed by a hiPri cellToRenderUpdate
|
|
1698
|
-
// We shouldn't do another hipri cellToRenderUpdate
|
|
1699
|
-
if (
|
|
1700
|
-
hiPri &&
|
|
1701
|
-
(this._averageCellLength || this.props.getItemLayout) &&
|
|
1702
|
-
!this._hiPriInProgress
|
|
1703
|
-
) {
|
|
1704
|
-
this._hiPriInProgress = true;
|
|
1705
|
-
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
|
|
1706
|
-
// as possible.
|
|
1707
|
-
this._updateCellsToRenderBatcher.dispose({abort: true});
|
|
1708
|
-
this._updateCellsToRender();
|
|
1709
|
-
return;
|
|
1710
|
-
} else {
|
|
1711
|
-
this._updateCellsToRenderBatcher.schedule();
|
|
1712
|
-
}
|
|
1830
|
+
|
|
1831
|
+
return hiPri;
|
|
1713
1832
|
}
|
|
1714
1833
|
|
|
1715
1834
|
_onScrollBeginDrag = (e: ScrollEvent): void => {
|
|
@@ -1758,6 +1877,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1758
1877
|
const cellsAroundViewport = this._adjustCellsAroundViewport(
|
|
1759
1878
|
props,
|
|
1760
1879
|
state.cellsAroundViewport,
|
|
1880
|
+
state.pendingScrollUpdateCount,
|
|
1761
1881
|
);
|
|
1762
1882
|
const renderMask = VirtualizedList._createRenderMask(
|
|
1763
1883
|
props,
|
|
@@ -1780,7 +1900,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1780
1900
|
_createViewToken = (
|
|
1781
1901
|
index: number,
|
|
1782
1902
|
isViewable: boolean,
|
|
1783
|
-
props:
|
|
1903
|
+
props: CellMetricProps,
|
|
1784
1904
|
// $FlowFixMe[missing-local-annot]
|
|
1785
1905
|
) => {
|
|
1786
1906
|
const {data, getItem} = props;
|
|
@@ -1788,87 +1908,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1788
1908
|
return {
|
|
1789
1909
|
index,
|
|
1790
1910
|
item,
|
|
1791
|
-
key:
|
|
1911
|
+
key: VirtualizedList._keyExtractor(item, index, props),
|
|
1792
1912
|
isViewable,
|
|
1793
1913
|
};
|
|
1794
1914
|
};
|
|
1795
1915
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
*/
|
|
1800
|
-
_getOffsetApprox = (index: number, props: FrameMetricProps): number => {
|
|
1801
|
-
if (Number.isInteger(index)) {
|
|
1802
|
-
return this.__getFrameMetricsApprox(index, props).offset;
|
|
1803
|
-
} else {
|
|
1804
|
-
const frameMetrics = this.__getFrameMetricsApprox(
|
|
1805
|
-
Math.floor(index),
|
|
1806
|
-
props,
|
|
1807
|
-
);
|
|
1808
|
-
const remainder = index - Math.floor(index);
|
|
1809
|
-
return frameMetrics.offset + remainder * frameMetrics.length;
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
|
-
|
|
1813
|
-
__getFrameMetricsApprox: (
|
|
1814
|
-
index: number,
|
|
1815
|
-
props: FrameMetricProps,
|
|
1816
|
-
) => {
|
|
1817
|
-
length: number,
|
|
1818
|
-
offset: number,
|
|
1819
|
-
...
|
|
1820
|
-
} = (index, props) => {
|
|
1821
|
-
const frame = this._getFrameMetrics(index, props);
|
|
1822
|
-
if (frame && frame.index === index) {
|
|
1823
|
-
// check for invalid frames due to row re-ordering
|
|
1824
|
-
return frame;
|
|
1825
|
-
} else {
|
|
1826
|
-
const {data, getItemCount, getItemLayout} = props;
|
|
1827
|
-
invariant(
|
|
1828
|
-
index >= 0 && index < getItemCount(data),
|
|
1829
|
-
'Tried to get frame for out of range index ' + index,
|
|
1830
|
-
);
|
|
1831
|
-
invariant(
|
|
1832
|
-
!getItemLayout,
|
|
1833
|
-
'Should not have to estimate frames when a measurement metrics function is provided',
|
|
1834
|
-
);
|
|
1835
|
-
return {
|
|
1836
|
-
length: this._averageCellLength,
|
|
1837
|
-
offset: this._averageCellLength * index,
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
};
|
|
1841
|
-
|
|
1842
|
-
_getFrameMetrics = (
|
|
1843
|
-
index: number,
|
|
1844
|
-
props: FrameMetricProps,
|
|
1845
|
-
): ?{
|
|
1846
|
-
length: number,
|
|
1847
|
-
offset: number,
|
|
1848
|
-
index: number,
|
|
1849
|
-
inLayout?: boolean,
|
|
1850
|
-
...
|
|
1851
|
-
} => {
|
|
1852
|
-
const {data, getItem, getItemCount, getItemLayout} = props;
|
|
1853
|
-
invariant(
|
|
1854
|
-
index >= 0 && index < getItemCount(data),
|
|
1855
|
-
'Tried to get frame for out of range index ' + index,
|
|
1856
|
-
);
|
|
1857
|
-
const item = getItem(data, index);
|
|
1858
|
-
const frame = this._frames[this._keyExtractor(item, index, props)];
|
|
1859
|
-
if (!frame || frame.index !== index) {
|
|
1860
|
-
if (getItemLayout) {
|
|
1861
|
-
/* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment
|
|
1862
|
-
* suppresses an error found when Flow v0.63 was deployed. To see the error
|
|
1863
|
-
* delete this comment and run Flow. */
|
|
1864
|
-
return getItemLayout(data, index);
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
return frame;
|
|
1868
|
-
};
|
|
1916
|
+
__getListMetrics(): ListMetricsAggregator {
|
|
1917
|
+
return this._listMetrics;
|
|
1918
|
+
}
|
|
1869
1919
|
|
|
1870
1920
|
_getNonViewportRenderRegions = (
|
|
1871
|
-
props:
|
|
1921
|
+
props: CellMetricProps,
|
|
1872
1922
|
): $ReadOnlyArray<{
|
|
1873
1923
|
first: number,
|
|
1874
1924
|
last: number,
|
|
@@ -1890,11 +1940,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1890
1940
|
// where it is.
|
|
1891
1941
|
if (
|
|
1892
1942
|
focusedCellIndex >= itemCount ||
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
focusedCellIndex,
|
|
1896
|
-
props,
|
|
1897
|
-
) !== this._lastFocusedCellKey
|
|
1943
|
+
VirtualizedList._getItemKey(props, focusedCellIndex) !==
|
|
1944
|
+
this._lastFocusedCellKey
|
|
1898
1945
|
) {
|
|
1899
1946
|
return [];
|
|
1900
1947
|
}
|
|
@@ -1907,7 +1954,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1907
1954
|
i--
|
|
1908
1955
|
) {
|
|
1909
1956
|
first--;
|
|
1910
|
-
heightOfCellsBeforeFocused += this.
|
|
1957
|
+
heightOfCellsBeforeFocused += this._listMetrics.getCellMetricsApprox(
|
|
1911
1958
|
i,
|
|
1912
1959
|
props,
|
|
1913
1960
|
).length;
|
|
@@ -1922,7 +1969,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1922
1969
|
i++
|
|
1923
1970
|
) {
|
|
1924
1971
|
last++;
|
|
1925
|
-
heightOfCellsAfterFocused += this.
|
|
1972
|
+
heightOfCellsAfterFocused += this._listMetrics.getCellMetricsApprox(
|
|
1926
1973
|
i,
|
|
1927
1974
|
props,
|
|
1928
1975
|
).length;
|
|
@@ -1932,15 +1979,20 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1932
1979
|
};
|
|
1933
1980
|
|
|
1934
1981
|
_updateViewableItems(
|
|
1935
|
-
props:
|
|
1982
|
+
props: CellMetricProps,
|
|
1936
1983
|
cellsAroundViewport: {first: number, last: number},
|
|
1937
1984
|
) {
|
|
1985
|
+
// If we have any pending scroll updates it means that the scroll metrics
|
|
1986
|
+
// are out of date and we should not call any of the visibility callbacks.
|
|
1987
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1938
1990
|
this._viewabilityTuples.forEach(tuple => {
|
|
1939
1991
|
tuple.viewabilityHelper.onUpdate(
|
|
1940
1992
|
props,
|
|
1941
1993
|
this._scrollMetrics.offset,
|
|
1942
1994
|
this._scrollMetrics.visibleLength,
|
|
1943
|
-
this.
|
|
1995
|
+
this._listMetrics,
|
|
1944
1996
|
this._createViewToken,
|
|
1945
1997
|
tuple.onViewableItemsChanged,
|
|
1946
1998
|
cellsAroundViewport,
|
|
@@ -1950,9 +2002,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1950
2002
|
}
|
|
1951
2003
|
|
|
1952
2004
|
const styles = StyleSheet.create({
|
|
1953
|
-
verticallyInverted:
|
|
1954
|
-
|
|
1955
|
-
|
|
2005
|
+
verticallyInverted:
|
|
2006
|
+
Platform.OS === 'android'
|
|
2007
|
+
? {transform: [{scale: -1}]}
|
|
2008
|
+
: {transform: [{scaleY: -1}]},
|
|
1956
2009
|
horizontallyInverted: {
|
|
1957
2010
|
transform: [{scaleX: -1}],
|
|
1958
2011
|
},
|