diff --git a/Examples/UIExplorer/js/FlatListExample.js b/Examples/UIExplorer/js/FlatListExample.js
index 83e754a56..527fb7ed9 100644
--- a/Examples/UIExplorer/js/FlatListExample.js
+++ b/Examples/UIExplorer/js/FlatListExample.js
@@ -40,6 +40,7 @@ const {
FooterComponent,
HeaderComponent,
ItemComponent,
+ ItemSeparatorComponent,
PlainInput,
SeparatorComponent,
Spindicator,
@@ -103,54 +104,59 @@ class FlatListExample extends React.PureComponent {
-
-
-
-
-
-
- {renderSmallSwitchOption(this, 'virtualized')}
- {renderSmallSwitchOption(this, 'horizontal')}
- {renderSmallSwitchOption(this, 'fixedHeight')}
- {renderSmallSwitchOption(this, 'logViewable')}
- {renderSmallSwitchOption(this, 'debug')}
-
+
+
+
+
+
+
+
+ {renderSmallSwitchOption(this, 'virtualized')}
+ {renderSmallSwitchOption(this, 'horizontal')}
+ {renderSmallSwitchOption(this, 'fixedHeight')}
+ {renderSmallSwitchOption(this, 'logViewable')}
+ {renderSmallSwitchOption(this, 'debug')}
+
+
+
+ }
+ ListFooterComponent={FooterComponent}
+ data={filteredData}
+ debug={this.state.debug}
+ disableVirtualization={!this.state.virtualized}
+ getItemLayout={this.state.fixedHeight ?
+ this._getItemLayout :
+ undefined
+ }
+ horizontal={this.state.horizontal}
+ key={(this.state.horizontal ? 'h' : 'v') +
+ (this.state.fixedHeight ? 'f' : 'd')
+ }
+ keyboardShouldPersistTaps="always"
+ keyboardDismissMode="on-drag"
+ legacyImplementation={false}
+ numColumns={1}
+ onEndReached={this._onEndReached}
+ onRefresh={this._onRefresh}
+ onScroll={this.state.horizontal ? this._scrollSinkX : this._scrollSinkY}
+ onViewableItemsChanged={this._onViewableItemsChanged}
+ ref={this._captureRef}
+ refreshing={false}
+ renderItem={this._renderItemComponent}
+ contentContainerStyle={styles.list}
+ viewabilityConfig={VIEWABILITY_CONFIG}
+ />
-
- }
- ListFooterComponent={FooterComponent}
- data={filteredData}
- debug={this.state.debug}
- disableVirtualization={!this.state.virtualized}
- getItemLayout={this.state.fixedHeight ?
- this._getItemLayout :
- undefined
- }
- horizontal={this.state.horizontal}
- key={(this.state.horizontal ? 'h' : 'v') +
- (this.state.fixedHeight ? 'f' : 'd')
- }
- legacyImplementation={false}
- numColumns={1}
- onEndReached={this._onEndReached}
- onRefresh={this._onRefresh}
- onScroll={this.state.horizontal ? this._scrollSinkX : this._scrollSinkY}
- onViewableItemsChanged={this._onViewableItemsChanged}
- ref={this._captureRef}
- refreshing={false}
- renderItem={this._renderItemComponent}
- viewabilityConfig={VIEWABILITY_CONFIG}
- />
);
}
@@ -159,18 +165,23 @@ class FlatListExample extends React.PureComponent {
return getItemLayout(data, index, this.state.horizontal);
};
_onEndReached = () => {
+ if (this.state.data.length >= 1000) {
+ return;
+ }
this.setState((state) => ({
data: state.data.concat(genItemData(100, state.data.length)),
}));
};
_onRefresh = () => alert('onRefresh: nothing to refresh :P');
- _renderItemComponent = ({item}) => {
+ _renderItemComponent = ({item, separators}) => {
return (
);
};
@@ -203,6 +214,13 @@ class FlatListExample extends React.PureComponent {
const styles = StyleSheet.create({
+ container: {
+ backgroundColor: 'rgb(239, 239, 244)',
+ flex: 1,
+ },
+ list: {
+ backgroundColor: 'white',
+ },
options: {
flexDirection: 'row',
flexWrap: 'wrap',
diff --git a/Examples/UIExplorer/js/ListExampleShared.js b/Examples/UIExplorer/js/ListExampleShared.js
index 6eae3fcb8..238386e41 100644
--- a/Examples/UIExplorer/js/ListExampleShared.js
+++ b/Examples/UIExplorer/js/ListExampleShared.js
@@ -54,6 +54,7 @@ function genItemData(count: number, start: number = 0): Array- {
}
const HORIZ_WIDTH = 200;
+const ITEM_HEIGHT = 72;
class ItemComponent extends React.PureComponent {
props: {
@@ -61,6 +62,8 @@ class ItemComponent extends React.PureComponent {
horizontal?: ?boolean,
item: Item,
onPress: (key: number) => void,
+ onShowUnderlay?: () => void,
+ onHideUnderlay?: () => void,
};
_onPress = () => {
this.props.onPress(this.props.item.key);
@@ -72,9 +75,11 @@ class ItemComponent extends React.PureComponent {
return (
+ styles.row, horizontal && {width: HORIZ_WIDTH}, fixedHeight && {height: ITEM_HEIGHT}]}>
{!item.noImage && }
{
class FooterComponent extends React.PureComponent {
render() {
return (
-
+
LIST FOOTER
@@ -114,7 +119,7 @@ class FooterComponent extends React.PureComponent {
class HeaderComponent extends React.PureComponent {
render() {
return (
-
+
LIST HEADER
@@ -130,6 +135,15 @@ class SeparatorComponent extends React.PureComponent {
}
}
+class ItemSeparatorComponent extends React.PureComponent {
+ render() {
+ const style = this.props.highlighted
+ ? [styles.itemSeparator, {marginLeft: 0, backgroundColor: 'rgb(217, 217, 217)'}]
+ : styles.itemSeparator;
+ return ;
+ }
+}
+
class Spindicator extends React.PureComponent {
render() {
return (
@@ -181,7 +195,7 @@ const SEPARATOR_HEIGHT = StyleSheet.hairlineWidth;
function getItemLayout(data: any, index: number, horizontal?: boolean) {
const [length, separator, header] = horizontal ?
- [HORIZ_WIDTH, 0, HEADER.width] : [84, SEPARATOR_HEIGHT, HEADER.height];
+ [HORIZ_WIDTH, 0, HEADER.width] : [ITEM_HEIGHT, SEPARATOR_HEIGHT, HEADER.height];
return {length, offset: (length + separator) * index + header, index};
}
@@ -231,12 +245,20 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
+ headerFooterContainer: {
+ backgroundColor: 'rgb(239, 239, 244)',
+ },
horizItem: {
alignSelf: 'flex-start', // Necessary for touch highlight
},
item: {
flex: 1,
},
+ itemSeparator: {
+ height: SEPARATOR_HEIGHT,
+ backgroundColor: 'rgb(200, 199, 204)',
+ marginLeft: 60,
+ },
option: {
flexDirection: 'row',
padding: 8,
@@ -245,7 +267,7 @@ const styles = StyleSheet.create({
row: {
flexDirection: 'row',
padding: 10,
- backgroundColor: '#F6F6F6',
+ backgroundColor: 'white',
},
searchTextInput: {
backgroundColor: 'white',
@@ -260,7 +282,7 @@ const styles = StyleSheet.create({
},
separator: {
height: SEPARATOR_HEIGHT,
- backgroundColor: 'gray',
+ backgroundColor: 'rgb(200, 199, 204)',
},
smallSwitch: Platform.select({
android: {
@@ -276,12 +298,13 @@ const styles = StyleSheet.create({
}),
stacked: {
alignItems: 'center',
- backgroundColor: '#F6F6F6',
+ backgroundColor: 'white',
padding: 10,
},
thumb: {
- width: 64,
- height: 64,
+ width: 50,
+ height: 50,
+ left: -5,
},
spindicator: {
marginLeft: 'auto',
@@ -303,6 +326,7 @@ module.exports = {
FooterComponent,
HeaderComponent,
ItemComponent,
+ ItemSeparatorComponent,
PlainInput,
SeparatorComponent,
Spindicator,
diff --git a/Examples/UIExplorer/js/MultiColumnExample.js b/Examples/UIExplorer/js/MultiColumnExample.js
index 3427c8aeb..5dfd192de 100644
--- a/Examples/UIExplorer/js/MultiColumnExample.js
+++ b/Examples/UIExplorer/js/MultiColumnExample.js
@@ -97,7 +97,6 @@ class MultiColumnExample extends React.PureComponent {
{
return (
-
+
+
+
);
};
// This is called when items change viewability by scrolling into or out of the viewable area.
@@ -142,7 +144,18 @@ class MultiColumnExample extends React.PureComponent {
};
}
+const CARD_MARGIN = 4;
+const BORDER_WIDTH = 1;
+
const styles = StyleSheet.create({
+ card: {
+ margin: CARD_MARGIN,
+ borderRadius: 10,
+ flex: 1,
+ overflow: 'hidden',
+ borderColor: 'lightgray',
+ borderWidth: BORDER_WIDTH,
+ },
row: {
flexDirection: 'row',
alignItems: 'center',
diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js
index 17ccf2ce8..af8511e66 100644
--- a/Libraries/Lists/FlatList.js
+++ b/Libraries/Lists/FlatList.js
@@ -24,19 +24,40 @@ import type {Props as VirtualizedListProps} from 'VirtualizedList';
type RequiredProps = {
/**
- * Takes an item from `data` and renders it into the list. Typical usage:
+ * Takes an item from `data` and renders it into the list. Example usage:
*
- * _renderItem = ({item}) => (
- * this._onPress(item)}>
- * {item.title}}
- *
- * );
- * ...
- *
+ * (
+ *
+ * )}
+ * data={[{title: 'Title Text', key: 'item1'}]}
+ * renderItem={({item, separators}) => (
+ * this._onPress(item)}
+ * onShowUnderlay={separators.highlight}
+ * onHideUnderlay={separators.unhighlight}>
+ *
+ * {item.title}}
+ *
+ *
+ * )}
+ * />
*
- * Provides additional metadata like `index` if you need it.
+ * Provides additional metadata like `index` if you need it, as well as a more generic
+ * `separators.updateProps` function which let's you set whatever props you want to change the
+ * rendering of either the leading separator or trailing separator in case the more common
+ * `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
+ * your use-case.
*/
- renderItem: (info: {item: ItemT, index: number}) => ?React.Element,
+ renderItem: (info: {
+ item: ItemT,
+ index: number,
+ separators: {
+ highlight: () => void,
+ unhighlight: () => void,
+ updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
+ },
+ }) => ?React.Element,
/**
* For simplicity, data is just a plain array. If you want to use something else, like an
* immutable list, use the underlying `VirtualizedList` directly.
@@ -45,7 +66,10 @@ type RequiredProps = {
};
type OptionalProps = {
/**
- * Rendered in between each item, but not at the top or bottom.
+ * Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
+ * `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
+ * which will update the `highlighted` prop, but you can also add custom props with
+ * `separators.updateProps`.
*/
ItemSeparatorComponent?: ?ReactClass,
/**
@@ -424,7 +448,7 @@ class FlatList extends React.PureComponent, vo
}
};
- _renderItem = (info: {item: ItemT | Array, index: number}) => {
+ _renderItem = (info: Object) => {
const {renderItem, numColumns, columnWrapperStyle} = this.props;
if (numColumns > 1) {
const {item, index} = info;
@@ -432,7 +456,11 @@ class FlatList extends React.PureComponent, vo
return (
{item.map((it, kk) => {
- const element = renderItem({item: it, index: index * numColumns + kk});
+ const element = renderItem({
+ item: it,
+ index: index * numColumns + kk,
+ separators: info.separators,
+ });
return element && React.cloneElement(element, {key: kk});
})}
diff --git a/Libraries/Lists/MetroListView.js b/Libraries/Lists/MetroListView.js
index eae4a6f32..125c60584 100644
--- a/Libraries/Lists/MetroListView.js
+++ b/Libraries/Lists/MetroListView.js
@@ -22,7 +22,7 @@ type Item = any;
type NormalProps = {
FooterComponent?: ReactClass<*>,
- renderItem: ({item: Item, index: number}) => ?React.Element<*>,
+ renderItem: (info: Object) => ?React.Element<*>,
renderSectionHeader?: ({section: Object}) => ?React.Element<*>,
SeparatorComponent?: ?ReactClass<*>, // not supported yet
diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js
index 2fa3fd3c0..b03d8198a 100644
--- a/Libraries/Lists/VirtualizedList.js
+++ b/Libraries/Lists/VirtualizedList.js
@@ -29,7 +29,15 @@ import type {ViewabilityConfig, ViewToken} from 'ViewabilityHelper';
type Item = any;
-type renderItemType = (info: {item: Item, index: number}) => ?React.Element;
+type renderItemType = (info: {
+ item: Item,
+ index: number,
+ separators: {
+ highlight: () => void,
+ unhighlight: () => void,
+ updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
+ },
+}) => ?React.Element;
type RequiredProps = {
renderItem: renderItemType,
@@ -290,13 +298,10 @@ class VirtualizedList extends React.PureComponent {
windowSize: 21, // multiples of length
};
- state: State = {
- first: 0,
- last: this.props.initialNumToRender,
- };
+ state: State;
- constructor(props: Props) {
- super(props);
+ constructor(props: Props, context: Object) {
+ super(props, context);
invariant(
!props.onScroll || !props.onScroll.__isNative,
'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' +
@@ -345,6 +350,7 @@ class VirtualizedList extends React.PureComponent {
const {ItemSeparatorComponent, data, getItem, getItemCount, keyExtractor} = this.props;
const stickyOffset = this.props.ListHeaderComponent ? 1 : 0;
const end = getItemCount(data) - 1;
+ let prevCellKey;
last = Math.min(end, last);
for (let ii = first; ii <= last; ii++) {
const item = getItem(data, ii);
@@ -356,20 +362,29 @@ class VirtualizedList extends React.PureComponent {
cells.push(
this._onCellLayout(e, key, ii)}
onUnmount={this._onCellUnmount}
parentProps={this.props}
+ ref={(ref) => {this._cellRefs[key] = ref;}}
/>
);
- if (ItemSeparatorComponent && ii < end) {
- cells.push();
- }
+ prevCellKey = key;
}
}
+ _onUpdateSeparators = (keys: Array, newProps: Object) => {
+ keys.forEach((key) => {
+ const ref = key != null && this._cellRefs[key];
+ ref && ref.updateSeparatorProps(newProps);
+ });
+ };
+
render() {
const {ListFooterComponent, ListHeaderComponent} = this.props;
const {data, disableVirtualization, horizontal} = this.props;
@@ -487,6 +502,7 @@ class VirtualizedList extends React.PureComponent {
}
_averageCellLength = 0;
+ _cellRefs = {};
_hasDataChangedSinceEndReached = true;
_hasWarned = {};
_highestMeasuredFrameIndex = 0;
@@ -776,34 +792,70 @@ class VirtualizedList extends React.PureComponent {
class CellRenderer extends React.Component {
props: {
+ ItemSeparatorComponent: ?ReactClass<*>,
cellKey: string,
index: number,
item: Item,
onLayout: (event: Object) => void, // This is extracted by ScrollViewStickyHeader
onUnmount: (cellKey: string) => void,
+ onUpdateSeparators: (cellKeys: Array, props: Object) => void,
parentProps: {
- renderItem: renderItemType,
getItemLayout?: ?Function,
+ renderItem: renderItemType,
+ },
+ prevCellKey: ?string,
+ };
+
+ state = {
+ separatorProps: {
+ highlighted: false,
+ leadingItem: this.props.item,
},
};
+
+ // TODO: consider factoring separator stuff out of VirtualizedList into FlatList since it's not
+ // reused by SectionList and we can keep VirtualizedList simpler.
+ _separators = {
+ highlight: () => {
+ const {cellKey, prevCellKey} = this.props;
+ this.props.onUpdateSeparators([cellKey, prevCellKey], {highlighted: true});
+ },
+ unhighlight: () => {
+ const {cellKey, prevCellKey} = this.props;
+ this.props.onUpdateSeparators([cellKey, prevCellKey], {highlighted: false});
+ },
+ updateProps: (select: 'leading' | 'trailing', newProps: Object) => {
+ const {cellKey, prevCellKey} = this.props;
+ this.props.onUpdateSeparators([select === 'leading' ? cellKey : prevCellKey], newProps);
+ },
+ };
+
+ updateSeparatorProps(newProps: Object) {
+ this.setState(state => ({separatorProps: {...state.separatorProps, ...newProps}}));
+ }
+
componentWillUnmount() {
this.props.onUnmount(this.props.cellKey);
}
+
render() {
- const {item, index, parentProps} = this.props;
+ const {ItemSeparatorComponent, item, index, parentProps} = this.props;
const {renderItem, getItemLayout} = parentProps;
invariant(renderItem, 'no renderItem!');
- const element = renderItem({item, index});
- if (getItemLayout &&
- !parentProps.debug &&
- !FillRateHelper.enabled()) {
- return element;
- }
+ const element = renderItem({
+ item,
+ index,
+ separators: this._separators,
+ });
+ const onLayout = (getItemLayout && !parentProps.debug && !FillRateHelper.enabled())
+ ? undefined
+ : this.props.onLayout;
// NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and
// called explicitly by `ScrollViewStickyHeader`.
return (
-
+
{element}
+ {ItemSeparatorComponent && }
);
}
diff --git a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap
index d96352abd..db1a896ae 100644
--- a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap
+++ b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap
@@ -63,54 +63,66 @@ exports[`FlatList renders all the bells and whistles 1`] = `
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
+
+
+