Skip to content

Commit 6209521

Browse files
Fix VirtualizedList with maintainVisibleContentPosition
1 parent b2fda3e commit 6209521

File tree

6 files changed

+190
-32
lines changed

6 files changed

+190
-32
lines changed

packages/rn-tester/js/components/ListExampleShared.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
const React = require('react');
1414

1515
const {
16+
ActivityIndicator,
1617
Animated,
1718
Image,
1819
Platform,
@@ -33,16 +34,28 @@ export type Item = {
3334
...
3435
};
3536

36-
function genItemData(count: number, start: number = 0): Array<Item> {
37+
function genItemData(i: number): Item {
38+
const itemHash = Math.abs(hashCode('Item ' + i));
39+
return {
40+
title: 'Item ' + i,
41+
text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20),
42+
key: String(i),
43+
pressed: false,
44+
};
45+
}
46+
47+
function genNewerItems(count: number, start: number = 0): Array<Item> {
48+
const dataBlob = [];
49+
for (let i = start; i < count + start; i++) {
50+
dataBlob.push(genItemData(i));
51+
}
52+
return dataBlob;
53+
}
54+
55+
function genOlderItems(count: number, start: number = 0): Array<Item> {
3756
const dataBlob = [];
38-
for (let ii = start; ii < count + start; ii++) {
39-
const itemHash = Math.abs(hashCode('Item ' + ii));
40-
dataBlob.push({
41-
title: 'Item ' + ii,
42-
text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20),
43-
key: String(ii),
44-
pressed: false,
45-
});
57+
for (let i = count; i > 0; i--) {
58+
dataBlob.push(genItemData(start - i));
4659
}
4760
return dataBlob;
4861
}
@@ -147,6 +160,12 @@ class SeparatorComponent extends React.PureComponent<{...}> {
147160
}
148161
}
149162

163+
const LoadingComponent: React.ComponentType<{}> = React.memo(() => (
164+
<View style={styles.loadingContainer}>
165+
<ActivityIndicator />
166+
</View>
167+
));
168+
150169
class ItemSeparatorComponent extends React.PureComponent<$FlowFixMeProps> {
151170
render(): React.Node {
152171
const style = this.props.highlighted
@@ -352,6 +371,13 @@ const styles = StyleSheet.create({
352371
text: {
353372
flex: 1,
354373
},
374+
loadingContainer: {
375+
alignItems: 'center',
376+
justifyContent: 'center',
377+
height: 100,
378+
borderTopWidth: 1,
379+
borderTopColor: 'rgb(200, 199, 204)',
380+
},
355381
});
356382

357383
module.exports = {
@@ -362,8 +388,10 @@ module.exports = {
362388
ItemSeparatorComponent,
363389
PlainInput,
364390
SeparatorComponent,
391+
LoadingComponent,
365392
Spindicator,
366-
genItemData,
393+
genNewerItems,
394+
genOlderItems,
367395
getItemLayout,
368396
pressItem,
369397
renderSmallSwitchOption,

packages/rn-tester/js/examples/FlatList/FlatList-basic.js

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,22 @@ import {
3535
ItemSeparatorComponent,
3636
PlainInput,
3737
SeparatorComponent,
38+
LoadingComponent,
3839
Spindicator,
39-
genItemData,
40+
genNewerItems,
41+
genOlderItems,
4042
getItemLayout,
4143
pressItem,
4244
renderSmallSwitchOption,
4345
} from '../../components/ListExampleShared';
4446

4547
import type {Item} from '../../components/ListExampleShared';
4648

49+
const PAGE_SIZE = 100;
50+
const NUM_PAGES = 10;
51+
const INITIAL_PAGE_OFFSET = Math.floor(NUM_PAGES / 2);
52+
const LOAD_TIME = 2000;
53+
4754
const VIEWABILITY_CONFIG = {
4855
minimumViewTime: 3000,
4956
viewAreaCoveragePercentThreshold: 100,
@@ -53,6 +60,8 @@ const VIEWABILITY_CONFIG = {
5360
type Props = $ReadOnly<{||}>;
5461
type State = {|
5562
data: Array<Item>,
63+
first: number,
64+
last: number,
5665
debug: boolean,
5766
horizontal: boolean,
5867
inverted: boolean,
@@ -66,13 +75,18 @@ type State = {|
6675
onPressDisabled: boolean,
6776
textSelectable: boolean,
6877
isRTL: boolean,
78+
maintainVisibleContentPosition: boolean,
79+
previousLoading: boolean,
80+
nextLoading: boolean,
6981
|};
7082

7183
const IS_RTL = I18nManager.isRTL;
7284

7385
class FlatListExample extends React.PureComponent<Props, State> {
7486
state: State = {
75-
data: genItemData(100),
87+
data: genNewerItems(PAGE_SIZE, PAGE_SIZE * INITIAL_PAGE_OFFSET),
88+
first: PAGE_SIZE * INITIAL_PAGE_OFFSET,
89+
last: PAGE_SIZE + PAGE_SIZE * INITIAL_PAGE_OFFSET,
7690
debug: false,
7791
horizontal: false,
7892
inverted: false,
@@ -86,6 +100,9 @@ class FlatListExample extends React.PureComponent<Props, State> {
86100
onPressDisabled: false,
87101
textSelectable: true,
88102
isRTL: IS_RTL,
103+
maintainVisibleContentPosition: true,
104+
previousLoading: false,
105+
nextLoading: false,
89106
};
90107

91108
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
@@ -209,6 +226,11 @@ class FlatListExample extends React.PureComponent<Props, State> {
209226
this.state.isRTL,
210227
this._setIsRTL,
211228
)}
229+
{renderSmallSwitchOption(
230+
'Maintain content position',
231+
this.state.maintainVisibleContentPosition,
232+
this._setBooleanValue('maintainVisibleContentPosition'),
233+
)}
212234
{Platform.OS === 'android' && (
213235
<View>
214236
<TextInput
@@ -230,8 +252,12 @@ class FlatListExample extends React.PureComponent<Props, State> {
230252
<Animated.FlatList
231253
fadingEdgeLength={this.state.fadingEdgeLength}
232254
ItemSeparatorComponent={ItemSeparatorComponent}
233-
ListHeaderComponent={<HeaderComponent />}
234-
ListFooterComponent={FooterComponent}
255+
ListHeaderComponent={
256+
this.state.previousLoading ? LoadingComponent : HeaderComponent
257+
}
258+
ListFooterComponent={
259+
this.state.nextLoading ? LoadingComponent : FooterComponent
260+
}
235261
ListEmptyComponent={ListEmptyComponent}
236262
// $FlowFixMe[missing-empty-array-annot]
237263
data={this.state.empty ? [] : filteredData}
@@ -250,6 +276,8 @@ class FlatListExample extends React.PureComponent<Props, State> {
250276
keyboardShouldPersistTaps="always"
251277
keyboardDismissMode="on-drag"
252278
numColumns={1}
279+
onStartReached={this._onStartReached}
280+
initialScrollIndex={Math.floor(PAGE_SIZE / 2)}
253281
onEndReached={this._onEndReached}
254282
onRefresh={this._onRefresh}
255283
onScroll={
@@ -260,6 +288,11 @@ class FlatListExample extends React.PureComponent<Props, State> {
260288
refreshing={false}
261289
contentContainerStyle={styles.list}
262290
viewabilityConfig={VIEWABILITY_CONFIG}
291+
maintainVisibleContentPosition={
292+
this.state.maintainVisibleContentPosition
293+
? {minIndexForVisible: 2}
294+
: undefined
295+
}
263296
{...flatListItemRendererProps}
264297
/>
265298
</View>
@@ -280,13 +313,33 @@ class FlatListExample extends React.PureComponent<Props, State> {
280313
_getItemLayout = (data: any, index: number) => {
281314
return getItemLayout(data, index, this.state.horizontal);
282315
};
316+
_onStartReached = () => {
317+
if (this.state.first <= 0 || this.state.previousLoading) {
318+
return;
319+
}
320+
321+
this.setState({previousLoading: true});
322+
setTimeout(() => {
323+
this.setState(state => ({
324+
previousLoading: false,
325+
data: genOlderItems(PAGE_SIZE, state.first).concat(state.data),
326+
first: state.first - PAGE_SIZE,
327+
}));
328+
}, LOAD_TIME);
329+
};
283330
_onEndReached = () => {
284-
if (this.state.data.length >= 1000) {
331+
if (this.state.last >= PAGE_SIZE * NUM_PAGES || this.state.nextLoading) {
285332
return;
286333
}
287-
this.setState(state => ({
288-
data: state.data.concat(genItemData(100, state.data.length)),
289-
}));
334+
335+
this.setState({nextLoading: true});
336+
setTimeout(() => {
337+
this.setState(state => ({
338+
nextLoading: false,
339+
data: state.data.concat(genNewerItems(PAGE_SIZE, state.last)),
340+
last: state.last + PAGE_SIZE,
341+
}));
342+
}, LOAD_TIME);
290343
};
291344
// $FlowFixMe[missing-local-annot]
292345
_onPressCallback = () => {
@@ -343,7 +396,7 @@ class FlatListExample extends React.PureComponent<Props, State> {
343396

344397
_pressItem = (key: string) => {
345398
this._listRef?.recordInteraction();
346-
const index = Number(key);
399+
const index = this.state.data.findIndex(item => item.key === key);
347400
const itemState = pressItem(this.state.data[index]);
348401
this.setState(state => ({
349402
...state,

packages/rn-tester/js/examples/FlatList/FlatList-multiColumn.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const {
2323
ItemComponent,
2424
PlainInput,
2525
SeparatorComponent,
26-
genItemData,
26+
genNewerItems,
2727
getItemLayout,
2828
pressItem,
2929
renderSmallSwitchOption,
@@ -46,7 +46,7 @@ class MultiColumnExample extends React.PureComponent<
4646
numColumns: number,
4747
virtualized: boolean,
4848
|} = {
49-
data: genItemData(1000),
49+
data: genNewerItems(1000),
5050
filterText: '',
5151
fixedHeight: true,
5252
logViewable: false,

packages/rn-tester/js/examples/SectionList/SectionList-scrollable.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const {
2222
PlainInput,
2323
SeparatorComponent,
2424
Spindicator,
25-
genItemData,
25+
genNewerItems,
2626
pressItem,
2727
renderSmallSwitchOption,
2828
renderStackedItem,
@@ -170,7 +170,7 @@ export function SectionList_scrollable(Props: {
170170
const [logViewable, setLogViewable] = React.useState(false);
171171
const [debug, setDebug] = React.useState(false);
172172
const [inverted, setInverted] = React.useState(false);
173-
const [data, setData] = React.useState(genItemData(1000));
173+
const [data, setData] = React.useState(genNewerItems(1000));
174174

175175
const filterRegex = new RegExp(String(filterText), 'i');
176176
const filter = (item: Item) =>

0 commit comments

Comments
 (0)