diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index ef2707138..a28faa0d2 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -131,6 +131,10 @@ type OptionalProps = { * `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 diff --git a/Libraries/Lists/SectionList.js b/Libraries/Lists/SectionList.js index 48a8a9e06..f351b8112 100644 --- a/Libraries/Lists/SectionList.js +++ b/Libraries/Lists/SectionList.js @@ -124,12 +124,15 @@ type OptionalProps> = { * 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 diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 596e9467a..9009b58c1 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -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 { stickyIndicesFromProps: Set, first: number, last: number, + inversionStyle: ?StyleObj, ) { const { ItemSeparatorComponent, @@ -449,6 +456,7 @@ class VirtualizedList extends React.PureComponent { cellKey={key} fillRateHelper={this._fillRateHelper} index={ii} + inversionStyle={inversionStyle} item={item} key={key} prevCellKey={prevCellKey} @@ -501,6 +509,11 @@ class VirtualizedList extends React.PureComponent { } = 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 { ? ListHeaderComponent // $FlowFixMe : ; cells.push( - + {element} , ); @@ -528,6 +544,7 @@ class VirtualizedList extends React.PureComponent { 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 { stickyIndicesFromProps, ii, ii, + inversionStyle, ); const trailSpace = this._getFrameMetricsApprox(first).offset - @@ -578,6 +596,7 @@ class VirtualizedList extends React.PureComponent { stickyIndicesFromProps, firstAfterInitial, last, + inversionStyle, ); if (!this._hasWarned.keys && _usedIndexForKey) { console.warn( @@ -608,7 +627,10 @@ class VirtualizedList extends React.PureComponent { ? ListEmptyComponent // $FlowFixMe : ; cells.push( - + {element} , ); @@ -618,7 +640,10 @@ class VirtualizedList extends React.PureComponent { ? ListFooterComponent // $FlowFixMe : ; cells.push( - + {element} , ); @@ -634,6 +659,9 @@ class VirtualizedList extends React.PureComponent { 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 ( - + {element} {ItemSeparatorComponent && } @@ -1170,4 +1200,13 @@ class CellRenderer extends React.Component { } } +const styles = StyleSheet.create({ + verticallyInverted: { + transform: [{scaleY: -1}], + }, + horizontallyInverted: { + transform: [{scaleX: -1}], + }, +}); + module.exports = VirtualizedList; diff --git a/Libraries/Lists/__tests__/VirtualizedList-test.js b/Libraries/Lists/__tests__/VirtualizedList-test.js index 5c6829591..057a940ff 100644 --- a/Libraries/Lists/__tests__/VirtualizedList-test.js +++ b/Libraries/Lists/__tests__/VirtualizedList-test.js @@ -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}) => } />, ); diff --git a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap index a8ed472ce..59e719584 100644 --- a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap +++ b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap @@ -61,11 +61,13 @@ exports[`FlatList renders all the bells and whistles 1`] = `