Add inverted option

Reviewed By: bvaughn

Differential Revision: D5210277

fbshipit-source-id: 5a8b196b90a2ac6d20397113ef4bd76446ea9fa3
This commit is contained in:
Spencer Ahrens 2017-06-12 22:33:00 -07:00 committed by Facebook Github Bot
parent 0a3e6e0e76
commit 1d30f833a6
8 changed files with 186 additions and 7 deletions

View File

@ -131,6 +131,10 @@ type OptionalProps<ItemT> = {
* `getItemLayout` to be implemented.
*/
initialScrollIndex?: ?number,
/**
* Reverses the direction of scroll. Uses scale transforms of -1.
*/
inverted?: ?boolean,
/**
* Used to extract a unique key for a given item at the specified index. Key is used for caching
* and as the react key to track item re-ordering. The default extractor checks `item.key`, then

View File

@ -124,12 +124,15 @@ type OptionalProps<SectionT: SectionBase<any>> = {
* to improve perceived performance of scroll-to-top actions.
*/
initialNumToRender: number,
/**
* Reverses the direction of scroll. Uses scale transforms of -1.
*/
inverted?: ?boolean,
/**
* Used to extract a unique key for a given item at the specified index. Key is used for caching
* and as the react key to track item re-ordering. The default extractor checks item.key, then
* falls back to using the index, like react does.
*/
keyExtractor: (item: Item, index: number) => string,
/**
* Called once when the scroll position gets within `onEndReachedThreshold` of the rendered

View File

@ -19,6 +19,7 @@ const React = require('React');
const ReactNative = require('ReactNative');
const RefreshControl = require('RefreshControl');
const ScrollView = require('ScrollView');
const StyleSheet = require('StyleSheet');
const View = require('View');
const ViewabilityHelper = require('ViewabilityHelper');
@ -29,6 +30,7 @@ const warning = require('fbjs/lib/warning');
const {computeWindowedRenderLimits} = require('VirtualizeUtils');
import type {StyleObj} from 'StyleSheetTypes';
import type {ViewabilityConfig, ViewToken} from 'ViewabilityHelper';
type Item = any;
@ -87,6 +89,10 @@ type OptionalProps = {
* `getItemLayout` to be implemented.
*/
initialScrollIndex?: ?number,
/**
* Reverses the direction of scroll. Uses scale transforms of -1.
*/
inverted?: ?boolean,
keyExtractor: (item: Item, index: number) => string,
/**
* Rendered when the list is empty. Can be a React Component Class, a render function, or
@ -424,6 +430,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
stickyIndicesFromProps: Set<number>,
first: number,
last: number,
inversionStyle: ?StyleObj,
) {
const {
ItemSeparatorComponent,
@ -449,6 +456,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
cellKey={key}
fillRateHelper={this._fillRateHelper}
index={ii}
inversionStyle={inversionStyle}
item={item}
key={key}
prevCellKey={prevCellKey}
@ -501,6 +509,11 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
} = this.props;
const {data, horizontal} = this.props;
const isVirtualizationDisabled = this._isVirtualizationDisabled();
const inversionStyle = this.props.inverted
? this.props.horizontal
? styles.horizontallyInverted
: styles.verticallyInverted
: null;
const cells = [];
const stickyIndicesFromProps = new Set(this.props.stickyHeaderIndices);
const stickyHeaderIndices = [];
@ -509,7 +522,10 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
? ListHeaderComponent // $FlowFixMe
: <ListHeaderComponent />;
cells.push(
<View key="$header" onLayout={this._onLayoutHeader}>
<View
key="$header"
onLayout={this._onLayoutHeader}
style={inversionStyle}>
{element}
</View>,
);
@ -528,6 +544,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
stickyIndicesFromProps,
0,
lastInitialIndex,
inversionStyle,
);
const firstAfterInitial = Math.max(lastInitialIndex + 1, first);
if (!isVirtualizationDisabled && first > lastInitialIndex + 1) {
@ -550,6 +567,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
stickyIndicesFromProps,
ii,
ii,
inversionStyle,
);
const trailSpace =
this._getFrameMetricsApprox(first).offset -
@ -578,6 +596,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
stickyIndicesFromProps,
firstAfterInitial,
last,
inversionStyle,
);
if (!this._hasWarned.keys && _usedIndexForKey) {
console.warn(
@ -608,7 +627,10 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
? ListEmptyComponent // $FlowFixMe
: <ListEmptyComponent />;
cells.push(
<View key="$empty" onLayout={this._onLayoutEmpty}>
<View
key="$empty"
onLayout={this._onLayoutEmpty}
style={inversionStyle}>
{element}
</View>,
);
@ -618,7 +640,10 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
? ListFooterComponent // $FlowFixMe
: <ListFooterComponent />;
cells.push(
<View key="$footer" onLayout={this._onLayoutFooter}>
<View
key="$footer"
onLayout={this._onLayoutFooter}
style={inversionStyle}>
{element}
</View>,
);
@ -634,6 +659,9 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support
stickyHeaderIndices,
};
if (inversionStyle) {
scrollProps.style = [inversionStyle, this.props.style];
}
const ret = React.cloneElement(
(this.props.renderScrollComponent || this._defaultRenderScrollComponent)(
scrollProps,
@ -1086,6 +1114,7 @@ class CellRenderer extends React.Component {
cellKey: string,
fillRateHelper: FillRateHelper,
index: number,
inversionStyle: ?StyleObj,
item: Item,
onLayout: (event: Object) => void, // This is extracted by ScrollViewStickyHeader
onUnmount: (cellKey: string) => void,
@ -1144,6 +1173,7 @@ class CellRenderer extends React.Component {
fillRateHelper,
item,
index,
inversionStyle,
parentProps,
} = this.props;
const {renderItem, getItemLayout} = parentProps;
@ -1161,7 +1191,7 @@ class CellRenderer extends React.Component {
// NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and
// called explicitly by `ScrollViewStickyHeader`.
return (
<View onLayout={onLayout}>
<View onLayout={onLayout} style={inversionStyle}>
{element}
{ItemSeparatorComponent &&
<ItemSeparatorComponent {...this.state.separatorProps} />}
@ -1170,4 +1200,13 @@ class CellRenderer extends React.Component {
}
}
const styles = StyleSheet.create({
verticallyInverted: {
transform: [{scaleY: -1}],
},
horizontallyInverted: {
transform: [{scaleX: -1}],
},
});
module.exports = VirtualizedList;

View File

@ -93,10 +93,11 @@ describe('VirtualizedList', () => {
data={new Array(5).fill().map((_, ii) => ({id: String(ii)}))}
getItem={(data, index) => data[index]}
getItemCount={data => data.length}
keyExtractor={(item, index) => item.id}
getItemLayout={({index}) => ({length: 50, offset: index * 50})}
refreshing={false}
inverted={true}
keyExtractor={(item, index) => item.id}
onRefresh={jest.fn()}
refreshing={false}
renderItem={({item}) => <item value={item.id} />}
/>,
);

View File

@ -61,11 +61,13 @@ exports[`FlatList renders all the bells and whistles 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<header />
</View>
<View
onLayout={undefined}
style={null}
>
<View
style={
@ -88,6 +90,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={undefined}
style={null}
>
<View
style={
@ -110,6 +113,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={undefined}
style={null}
>
<View
style={
@ -128,6 +132,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<footer />
</View>
@ -233,6 +238,7 @@ exports[`FlatList renders simple list 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
value="i1"
@ -240,6 +246,7 @@ exports[`FlatList renders simple list 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
value="i2"
@ -247,6 +254,7 @@ exports[`FlatList renders simple list 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
value="i3"

View File

@ -63,9 +63,11 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
/>
<View
onLayout={[Function]}
style={null}
>
<item
v="i1"
@ -73,6 +75,7 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
v="i2"
@ -80,6 +83,7 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
/>
</View>
</RCTScrollView>
@ -135,6 +139,7 @@ exports[`SectionList renders a footer when there is no data 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<sectionHeader
v="section:s1"
@ -142,6 +147,7 @@ exports[`SectionList renders a footer when there is no data 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionFooter
v="section:s1"
@ -200,9 +206,11 @@ exports[`SectionList renders a footer when there is no data and no header 1`] =
<View>
<View
onLayout={[Function]}
style={null}
/>
<View
onLayout={[Function]}
style={null}
>
<sectionFooter
v="section:s1"
@ -342,6 +350,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<header
v=""
@ -349,6 +358,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionHeader
v="section:s1"
@ -356,6 +366,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<View>
<sectionSeparator
@ -371,6 +382,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<View>
<itemForSection1
@ -383,6 +395,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionFooter
v="section:s1"
@ -390,6 +403,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionHeader
v="section:s2"
@ -397,6 +411,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<View>
<sectionSeparator
@ -412,6 +427,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<View>
<defaultItem
@ -424,6 +440,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionFooter
v="section:s2"
@ -431,6 +448,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionHeader
v="section:s3"
@ -438,6 +456,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<View>
<sectionSeparator
@ -453,6 +472,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<View>
<defaultItem
@ -465,6 +485,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<sectionFooter
v="section:s3"
@ -472,6 +493,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<footer
v=""

View File

@ -35,6 +35,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<View
data={
@ -69,6 +70,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
>
<View
onLayout={[Function]}
style={null}
>
<item
title="outer0:inner0"
@ -76,6 +78,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="outer0:inner1"
@ -85,6 +88,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<RCTScrollView
data={
@ -120,6 +124,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
title="outer1:inner0"
@ -127,6 +132,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="outer1:inner1"
@ -178,6 +184,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i0"
@ -193,6 +200,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i1"
@ -208,6 +216,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i2"
@ -256,6 +265,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i0"
@ -271,6 +281,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i1"
@ -286,6 +297,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i2"
@ -334,6 +346,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i0"
@ -349,6 +362,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i1"
@ -365,6 +379,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
title="i2"
@ -405,6 +420,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
getItemLayout={[Function]}
horizontal={false}
initialNumToRender={10}
inverted={true}
keyExtractor={[Function]}
maxToRenderPerBatch={10}
onContentSizeChange={[Function]}
@ -426,6 +442,18 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
style={
Array [
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
},
undefined,
]
}
updateCellsBatchingPeriod={50}
windowSize={21}
>
@ -433,11 +461,29 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
<View>
<View
onLayout={[Function]}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<header />
</View>
<View
onLayout={undefined}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<item
value="0"
@ -446,6 +492,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={undefined}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<item
value="1"
@ -454,6 +509,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={undefined}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<item
value="2"
@ -462,6 +526,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={undefined}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<item
value="3"
@ -470,6 +543,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={undefined}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<item
value="4"
@ -477,6 +559,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
</View>
<View
onLayout={[Function]}
style={
Object {
"transform": Array [
Object {
"scaleY": -1,
},
],
}
}
>
<footer />
</View>
@ -540,16 +631,19 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<header />
</View>
<View
onLayout={[Function]}
style={null}
>
<empty />
</View>
<View
onLayout={[Function]}
style={null}
>
<footer />
</View>
@ -590,6 +684,7 @@ exports[`VirtualizedList renders list with empty component 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
value="hello"
@ -664,6 +759,7 @@ exports[`VirtualizedList renders simple list 1`] = `
<View>
<View
onLayout={[Function]}
style={null}
>
<item
value="i1"
@ -671,6 +767,7 @@ exports[`VirtualizedList renders simple list 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
value="i2"
@ -678,6 +775,7 @@ exports[`VirtualizedList renders simple list 1`] = `
</View>
<View
onLayout={[Function]}
style={null}
>
<item
value="i3"
@ -719,6 +817,7 @@ exports[`VirtualizedList test getItem functionality where data is not an Array 1
<View>
<View
onLayout={[Function]}
style={null}
>
<item
value="item_0"

View File

@ -55,6 +55,7 @@ class FlatListExample extends React.PureComponent {
data: genItemData(100),
debug: false,
horizontal: false,
inverted: false,
filterText: '',
fixedHeight: true,
logViewable: false,
@ -111,6 +112,7 @@ class FlatListExample extends React.PureComponent {
{renderSmallSwitchOption(this, 'horizontal')}
{renderSmallSwitchOption(this, 'fixedHeight')}
{renderSmallSwitchOption(this, 'logViewable')}
{renderSmallSwitchOption(this, 'inverted')}
{renderSmallSwitchOption(this, 'debug')}
<Spindicator value={this._scrollPos} />
</View>
@ -128,6 +130,7 @@ class FlatListExample extends React.PureComponent {
undefined
}
horizontal={this.state.horizontal}
inverted={this.state.inverted}
key={(this.state.horizontal ? 'h' : 'v') +
(this.state.fixedHeight ? 'f' : 'd')
}