@react-native/virtualized-lists 0.73.0 → 0.73.2
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 +303 -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 +359 -335
- 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,37 +1290,23 @@ 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
|
-
index !== curr.index
|
|
1239
|
-
) {
|
|
1240
|
-
this._totalCellLength += next.length - (curr ? curr.length : 0);
|
|
1241
|
-
this._totalCellsMeasured += curr ? 0 : 1;
|
|
1242
|
-
this._averageCellLength =
|
|
1243
|
-
this._totalCellLength / this._totalCellsMeasured;
|
|
1244
|
-
this._frames[cellKey] = next;
|
|
1245
|
-
this._highestMeasuredFrameIndex = Math.max(
|
|
1246
|
-
this._highestMeasuredFrameIndex,
|
|
1247
|
-
index,
|
|
1248
|
-
);
|
|
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
|
+
if (layoutHasChanged) {
|
|
1249
1306
|
this._scheduleCellsToRenderUpdate();
|
|
1250
|
-
} else {
|
|
1251
|
-
this._frames[cellKey].inLayout = true;
|
|
1252
1307
|
}
|
|
1253
1308
|
|
|
1254
1309
|
this._triggerRemeasureForChildListsInCell(cellKey);
|
|
1255
|
-
|
|
1256
1310
|
this._computeBlankness();
|
|
1257
1311
|
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
|
1258
1312
|
};
|
|
@@ -1264,10 +1318,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1264
1318
|
|
|
1265
1319
|
_onCellUnmount = (cellKey: string) => {
|
|
1266
1320
|
delete this._cellRefs[cellKey];
|
|
1267
|
-
|
|
1268
|
-
if (curr) {
|
|
1269
|
-
this._frames[cellKey] = {...curr, inLayout: false};
|
|
1270
|
-
}
|
|
1321
|
+
this._listMetrics.notifyCellUnmounted(cellKey);
|
|
1271
1322
|
};
|
|
1272
1323
|
|
|
1273
1324
|
_triggerRemeasureForChildListsInCell(cellKey: string): void {
|
|
@@ -1289,9 +1340,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1289
1340
|
this.context.getOutermostParentListRef().getScrollRef(),
|
|
1290
1341
|
(x, y, width, height) => {
|
|
1291
1342
|
this._offsetFromParentVirtualizedList = this._selectOffset({x, y});
|
|
1292
|
-
this.
|
|
1293
|
-
width,
|
|
1294
|
-
|
|
1343
|
+
this._listMetrics.notifyListContentLayout({
|
|
1344
|
+
layout: {width, height},
|
|
1345
|
+
orientation: this._orientation(),
|
|
1295
1346
|
});
|
|
1296
1347
|
const scrollMetrics = this._convertParentScrollMetrics(
|
|
1297
1348
|
this.context.getScrollMetrics(),
|
|
@@ -1363,23 +1414,20 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1363
1414
|
_renderDebugOverlay() {
|
|
1364
1415
|
const normalize =
|
|
1365
1416
|
this._scrollMetrics.visibleLength /
|
|
1366
|
-
(this.
|
|
1417
|
+
(this._listMetrics.getContentLength() || 1);
|
|
1367
1418
|
const framesInLayout = [];
|
|
1368
1419
|
const itemCount = this.props.getItemCount(this.props.data);
|
|
1369
1420
|
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) {
|
|
1421
|
+
const frame = this._listMetrics.getCellMetricsApprox(ii, this.props);
|
|
1422
|
+
if (frame.isMounted) {
|
|
1375
1423
|
framesInLayout.push(frame);
|
|
1376
1424
|
}
|
|
1377
1425
|
}
|
|
1378
|
-
const windowTop = this.
|
|
1426
|
+
const windowTop = this._listMetrics.getCellMetricsApprox(
|
|
1379
1427
|
this.state.cellsAroundViewport.first,
|
|
1380
1428
|
this.props,
|
|
1381
1429
|
).offset;
|
|
1382
|
-
const frameLast = this.
|
|
1430
|
+
const frameLast = this._listMetrics.getCellMetricsApprox(
|
|
1383
1431
|
this.state.cellsAroundViewport.last,
|
|
1384
1432
|
this.props,
|
|
1385
1433
|
);
|
|
@@ -1438,14 +1486,15 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1438
1486
|
: metrics.width;
|
|
1439
1487
|
}
|
|
1440
1488
|
|
|
1441
|
-
_selectOffset(
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1489
|
+
_selectOffset({x, y}: $ReadOnly<{x: number, y: number, ...}>): number {
|
|
1490
|
+
return this._orientation().horizontal ? x : y;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
_orientation(): ListOrientation {
|
|
1494
|
+
return {
|
|
1495
|
+
horizontal: horizontalOrDefault(this.props.horizontal),
|
|
1496
|
+
rtl: I18nManager.isRTL,
|
|
1497
|
+
};
|
|
1449
1498
|
}
|
|
1450
1499
|
|
|
1451
1500
|
_maybeCallOnEdgeReached() {
|
|
@@ -1456,11 +1505,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1456
1505
|
onStartReachedThreshold,
|
|
1457
1506
|
onEndReached,
|
|
1458
1507
|
onEndReachedThreshold,
|
|
1459
|
-
initialScrollIndex,
|
|
1460
1508
|
} = this.props;
|
|
1461
|
-
|
|
1509
|
+
// If we have any pending scroll updates it means that the scroll metrics
|
|
1510
|
+
// are out of date and we should not call any of the edge reached callbacks.
|
|
1511
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
const {visibleLength, offset} = this._scrollMetrics;
|
|
1462
1516
|
let distanceFromStart = offset;
|
|
1463
|
-
let distanceFromEnd =
|
|
1517
|
+
let distanceFromEnd =
|
|
1518
|
+
this._listMetrics.getContentLength() - visibleLength - offset;
|
|
1464
1519
|
|
|
1465
1520
|
// Especially when oERT is zero it's necessary to 'floor' very small distance values to be 0
|
|
1466
1521
|
// since debouncing causes us to not fire this event for every single "pixel" we scroll and can thus
|
|
@@ -1494,9 +1549,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1494
1549
|
onEndReached &&
|
|
1495
1550
|
this.state.cellsAroundViewport.last === getItemCount(data) - 1 &&
|
|
1496
1551
|
isWithinEndThreshold &&
|
|
1497
|
-
this.
|
|
1552
|
+
this._listMetrics.getContentLength() !== this._sentEndForContentLength
|
|
1498
1553
|
) {
|
|
1499
|
-
this._sentEndForContentLength = this.
|
|
1554
|
+
this._sentEndForContentLength = this._listMetrics.getContentLength();
|
|
1500
1555
|
onEndReached({distanceFromEnd});
|
|
1501
1556
|
}
|
|
1502
1557
|
|
|
@@ -1507,16 +1562,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1507
1562
|
onStartReached != null &&
|
|
1508
1563
|
this.state.cellsAroundViewport.first === 0 &&
|
|
1509
1564
|
isWithinStartThreshold &&
|
|
1510
|
-
this.
|
|
1565
|
+
this._listMetrics.getContentLength() !== this._sentStartForContentLength
|
|
1511
1566
|
) {
|
|
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
|
-
}
|
|
1567
|
+
this._sentStartForContentLength = this._listMetrics.getContentLength();
|
|
1568
|
+
onStartReached({distanceFromStart});
|
|
1520
1569
|
}
|
|
1521
1570
|
|
|
1522
1571
|
// If the user scrolls away from the start or end and back again,
|
|
@@ -1532,9 +1581,32 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1532
1581
|
}
|
|
1533
1582
|
|
|
1534
1583
|
_onContentSizeChange = (width: number, height: number) => {
|
|
1584
|
+
this._listMetrics.notifyListContentLayout({
|
|
1585
|
+
layout: {width, height},
|
|
1586
|
+
orientation: this._orientation(),
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
this._maybeScrollToInitialScrollIndex(width, height);
|
|
1590
|
+
|
|
1591
|
+
if (this.props.onContentSizeChange) {
|
|
1592
|
+
this.props.onContentSizeChange(width, height);
|
|
1593
|
+
}
|
|
1594
|
+
this._scheduleCellsToRenderUpdate();
|
|
1595
|
+
this._maybeCallOnEdgeReached();
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* Scroll to a specified `initialScrollIndex` prop after the ScrollView
|
|
1600
|
+
* content has been laid out, if it is still valid. Only a single scroll is
|
|
1601
|
+
* triggered throughout the lifetime of the list.
|
|
1602
|
+
*/
|
|
1603
|
+
_maybeScrollToInitialScrollIndex(
|
|
1604
|
+
contentWidth: number,
|
|
1605
|
+
contentHeight: number,
|
|
1606
|
+
) {
|
|
1535
1607
|
if (
|
|
1536
|
-
|
|
1537
|
-
|
|
1608
|
+
contentWidth > 0 &&
|
|
1609
|
+
contentHeight > 0 &&
|
|
1538
1610
|
this.props.initialScrollIndex != null &&
|
|
1539
1611
|
this.props.initialScrollIndex > 0 &&
|
|
1540
1612
|
!this._hasTriggeredInitialScrollToIndex
|
|
@@ -1554,13 +1626,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1554
1626
|
}
|
|
1555
1627
|
this._hasTriggeredInitialScrollToIndex = true;
|
|
1556
1628
|
}
|
|
1557
|
-
|
|
1558
|
-
this.props.onContentSizeChange(width, height);
|
|
1559
|
-
}
|
|
1560
|
-
this._scrollMetrics.contentLength = this._selectLength({height, width});
|
|
1561
|
-
this._scheduleCellsToRenderUpdate();
|
|
1562
|
-
this._maybeCallOnEdgeReached();
|
|
1563
|
-
};
|
|
1629
|
+
}
|
|
1564
1630
|
|
|
1565
1631
|
/* Translates metrics from a scroll event in a parent VirtualizedList into
|
|
1566
1632
|
* coordinates relative to the child list.
|
|
@@ -1575,7 +1641,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1575
1641
|
// Child's visible length is the same as its parent's
|
|
1576
1642
|
const visibleLength = metrics.visibleLength;
|
|
1577
1643
|
const dOffset = offset - this._scrollMetrics.offset;
|
|
1578
|
-
const contentLength = this.
|
|
1644
|
+
const contentLength = this._listMetrics.getContentLength();
|
|
1579
1645
|
|
|
1580
1646
|
return {
|
|
1581
1647
|
visibleLength,
|
|
@@ -1595,11 +1661,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1595
1661
|
const timestamp = e.timeStamp;
|
|
1596
1662
|
let visibleLength = this._selectLength(e.nativeEvent.layoutMeasurement);
|
|
1597
1663
|
let contentLength = this._selectLength(e.nativeEvent.contentSize);
|
|
1598
|
-
let offset = this.
|
|
1664
|
+
let offset = this._offsetFromScrollEvent(e);
|
|
1599
1665
|
let dOffset = offset - this._scrollMetrics.offset;
|
|
1600
1666
|
|
|
1601
1667
|
if (this._isNestedWithSameOrientation()) {
|
|
1602
|
-
if (this.
|
|
1668
|
+
if (this._listMetrics.getContentLength() === 0) {
|
|
1603
1669
|
// Ignore scroll events until onLayout has been called and we
|
|
1604
1670
|
// know our offset from our offset from our parent
|
|
1605
1671
|
return;
|
|
@@ -1634,7 +1700,6 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1634
1700
|
// For invalid negative values (w/ RTL), set this to 1.
|
|
1635
1701
|
const zoomScale = e.nativeEvent.zoomScale < 0 ? 1 : e.nativeEvent.zoomScale;
|
|
1636
1702
|
this._scrollMetrics = {
|
|
1637
|
-
contentLength,
|
|
1638
1703
|
dt,
|
|
1639
1704
|
dOffset,
|
|
1640
1705
|
offset,
|
|
@@ -1643,6 +1708,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1643
1708
|
visibleLength,
|
|
1644
1709
|
zoomScale,
|
|
1645
1710
|
};
|
|
1711
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1712
|
+
this.setState(state => ({
|
|
1713
|
+
pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1,
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1646
1716
|
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
|
1647
1717
|
if (!this.props) {
|
|
1648
1718
|
return;
|
|
@@ -1655,7 +1725,45 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1655
1725
|
this._scheduleCellsToRenderUpdate();
|
|
1656
1726
|
};
|
|
1657
1727
|
|
|
1728
|
+
_offsetFromScrollEvent(e: ScrollEvent): number {
|
|
1729
|
+
const {contentOffset, contentSize, layoutMeasurement} = e.nativeEvent;
|
|
1730
|
+
const {horizontal, rtl} = this._orientation();
|
|
1731
|
+
if (horizontal && rtl) {
|
|
1732
|
+
return (
|
|
1733
|
+
this._selectLength(contentSize) -
|
|
1734
|
+
(this._selectOffset(contentOffset) +
|
|
1735
|
+
this._selectLength(layoutMeasurement))
|
|
1736
|
+
);
|
|
1737
|
+
} else {
|
|
1738
|
+
return this._selectOffset(contentOffset);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1658
1742
|
_scheduleCellsToRenderUpdate() {
|
|
1743
|
+
// Only trigger high-priority updates if we've actually rendered cells,
|
|
1744
|
+
// and with that size estimate, accurately compute how many cells we should render.
|
|
1745
|
+
// Otherwise, it would just render as many cells as it can (of zero dimension),
|
|
1746
|
+
// each time through attempting to render more (limited by maxToRenderPerBatch),
|
|
1747
|
+
// starving the renderer from actually laying out the objects and computing _averageCellLength.
|
|
1748
|
+
// If this is triggered in an `componentDidUpdate` followed by a hiPri cellToRenderUpdate
|
|
1749
|
+
// We shouldn't do another hipri cellToRenderUpdate
|
|
1750
|
+
if (
|
|
1751
|
+
this._shouldRenderWithPriority() &&
|
|
1752
|
+
(this._listMetrics.getAverageCellLength() || this.props.getItemLayout) &&
|
|
1753
|
+
!this._hiPriInProgress
|
|
1754
|
+
) {
|
|
1755
|
+
this._hiPriInProgress = true;
|
|
1756
|
+
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
|
|
1757
|
+
// as possible.
|
|
1758
|
+
this._updateCellsToRenderBatcher.dispose({abort: true});
|
|
1759
|
+
this._updateCellsToRender();
|
|
1760
|
+
return;
|
|
1761
|
+
} else {
|
|
1762
|
+
this._updateCellsToRenderBatcher.schedule();
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
_shouldRenderWithPriority(): boolean {
|
|
1659
1767
|
const {first, last} = this.state.cellsAroundViewport;
|
|
1660
1768
|
const {offset, visibleLength, velocity} = this._scrollMetrics;
|
|
1661
1769
|
const itemCount = this.props.getItemCount(this.props.data);
|
|
@@ -1670,7 +1778,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1670
1778
|
// But only if there are items before the first rendered item
|
|
1671
1779
|
if (first > 0) {
|
|
1672
1780
|
const distTop =
|
|
1673
|
-
offset -
|
|
1781
|
+
offset -
|
|
1782
|
+
this._listMetrics.getCellMetricsApprox(first, this.props).offset;
|
|
1674
1783
|
hiPri =
|
|
1675
1784
|
distTop < 0 ||
|
|
1676
1785
|
(velocity < -2 &&
|
|
@@ -1681,7 +1790,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1681
1790
|
// But only if there are items after the last rendered item
|
|
1682
1791
|
if (!hiPri && last >= 0 && last < itemCount - 1) {
|
|
1683
1792
|
const distBottom =
|
|
1684
|
-
this.
|
|
1793
|
+
this._listMetrics.getCellMetricsApprox(last, this.props).offset -
|
|
1685
1794
|
(offset + visibleLength);
|
|
1686
1795
|
hiPri =
|
|
1687
1796
|
distBottom < 0 ||
|
|
@@ -1689,27 +1798,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1689
1798
|
distBottom <
|
|
1690
1799
|
getScrollingThreshold(onEndReachedThreshold, visibleLength));
|
|
1691
1800
|
}
|
|
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
|
-
}
|
|
1801
|
+
|
|
1802
|
+
return hiPri;
|
|
1713
1803
|
}
|
|
1714
1804
|
|
|
1715
1805
|
_onScrollBeginDrag = (e: ScrollEvent): void => {
|
|
@@ -1758,6 +1848,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1758
1848
|
const cellsAroundViewport = this._adjustCellsAroundViewport(
|
|
1759
1849
|
props,
|
|
1760
1850
|
state.cellsAroundViewport,
|
|
1851
|
+
state.pendingScrollUpdateCount,
|
|
1761
1852
|
);
|
|
1762
1853
|
const renderMask = VirtualizedList._createRenderMask(
|
|
1763
1854
|
props,
|
|
@@ -1780,7 +1871,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1780
1871
|
_createViewToken = (
|
|
1781
1872
|
index: number,
|
|
1782
1873
|
isViewable: boolean,
|
|
1783
|
-
props:
|
|
1874
|
+
props: CellMetricProps,
|
|
1784
1875
|
// $FlowFixMe[missing-local-annot]
|
|
1785
1876
|
) => {
|
|
1786
1877
|
const {data, getItem} = props;
|
|
@@ -1788,87 +1879,17 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1788
1879
|
return {
|
|
1789
1880
|
index,
|
|
1790
1881
|
item,
|
|
1791
|
-
key:
|
|
1882
|
+
key: VirtualizedList._keyExtractor(item, index, props),
|
|
1792
1883
|
isViewable,
|
|
1793
1884
|
};
|
|
1794
1885
|
};
|
|
1795
1886
|
|
|
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
|
-
};
|
|
1887
|
+
__getListMetrics(): ListMetricsAggregator {
|
|
1888
|
+
return this._listMetrics;
|
|
1889
|
+
}
|
|
1869
1890
|
|
|
1870
1891
|
_getNonViewportRenderRegions = (
|
|
1871
|
-
props:
|
|
1892
|
+
props: CellMetricProps,
|
|
1872
1893
|
): $ReadOnlyArray<{
|
|
1873
1894
|
first: number,
|
|
1874
1895
|
last: number,
|
|
@@ -1890,11 +1911,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1890
1911
|
// where it is.
|
|
1891
1912
|
if (
|
|
1892
1913
|
focusedCellIndex >= itemCount ||
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
focusedCellIndex,
|
|
1896
|
-
props,
|
|
1897
|
-
) !== this._lastFocusedCellKey
|
|
1914
|
+
VirtualizedList._getItemKey(props, focusedCellIndex) !==
|
|
1915
|
+
this._lastFocusedCellKey
|
|
1898
1916
|
) {
|
|
1899
1917
|
return [];
|
|
1900
1918
|
}
|
|
@@ -1907,7 +1925,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1907
1925
|
i--
|
|
1908
1926
|
) {
|
|
1909
1927
|
first--;
|
|
1910
|
-
heightOfCellsBeforeFocused += this.
|
|
1928
|
+
heightOfCellsBeforeFocused += this._listMetrics.getCellMetricsApprox(
|
|
1911
1929
|
i,
|
|
1912
1930
|
props,
|
|
1913
1931
|
).length;
|
|
@@ -1922,7 +1940,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1922
1940
|
i++
|
|
1923
1941
|
) {
|
|
1924
1942
|
last++;
|
|
1925
|
-
heightOfCellsAfterFocused += this.
|
|
1943
|
+
heightOfCellsAfterFocused += this._listMetrics.getCellMetricsApprox(
|
|
1926
1944
|
i,
|
|
1927
1945
|
props,
|
|
1928
1946
|
).length;
|
|
@@ -1932,15 +1950,20 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1932
1950
|
};
|
|
1933
1951
|
|
|
1934
1952
|
_updateViewableItems(
|
|
1935
|
-
props:
|
|
1953
|
+
props: CellMetricProps,
|
|
1936
1954
|
cellsAroundViewport: {first: number, last: number},
|
|
1937
1955
|
) {
|
|
1956
|
+
// If we have any pending scroll updates it means that the scroll metrics
|
|
1957
|
+
// are out of date and we should not call any of the visibility callbacks.
|
|
1958
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1938
1961
|
this._viewabilityTuples.forEach(tuple => {
|
|
1939
1962
|
tuple.viewabilityHelper.onUpdate(
|
|
1940
1963
|
props,
|
|
1941
1964
|
this._scrollMetrics.offset,
|
|
1942
1965
|
this._scrollMetrics.visibleLength,
|
|
1943
|
-
this.
|
|
1966
|
+
this._listMetrics,
|
|
1944
1967
|
this._createViewToken,
|
|
1945
1968
|
tuple.onViewableItemsChanged,
|
|
1946
1969
|
cellsAroundViewport,
|
|
@@ -1950,9 +1973,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1950
1973
|
}
|
|
1951
1974
|
|
|
1952
1975
|
const styles = StyleSheet.create({
|
|
1953
|
-
verticallyInverted:
|
|
1954
|
-
|
|
1955
|
-
|
|
1976
|
+
verticallyInverted:
|
|
1977
|
+
Platform.OS === 'android'
|
|
1978
|
+
? {transform: [{scale: -1}]}
|
|
1979
|
+
: {transform: [{scaleY: -1}]},
|
|
1956
1980
|
horizontallyInverted: {
|
|
1957
1981
|
transform: [{scaleX: -1}],
|
|
1958
1982
|
},
|