Add support for updating adjacent separators on row highlight to FlatList
Summary: A nice bit of polish for apps is to update the separators between list items when press actives the highlight (things get especially noticeable/ugly when the separators are not full width and don't adjust). This can be difficult to coordinate and update efficiently, so we bake the functionality into `VirtualizedList`. The approach taken here is a little different from `ListView`. We pass a new `separators` object with `renderItem` that has easy-to-use callbacks for toggling the 'highlighted' prop on both adjacent separators - they can be wired up directly to the `onShow/HideUnderlay` props of `TouchableHighlight` (pit of success and all that - we want those RN apps to be polished!), but we also provide a more generic `separators.updateProps` method to set any custom props. This also passes in `leadingItem` so more custom wiring can be done on initial render (e.g. linking the highlight state with `Animated`). This also moves the separator rendering into the `CellRenderer`. I think this might also fix some subtle measurement bugs with the `onLayout` not capturing the height of the separator, so that's nice too, but the main reason is to have an easy place to store the state so we don't have to re-render the entire list like `ListView` does. Instead we track references to the cells and call update only on the two we care about so the feedback is instantaneous even with big, heavy lists. This diff also messes with a bunch of flow and updates the example to be more like a standard list. `SectionList` support is coming in a stacked diff. **TestPlan** Video demo: https://youtu.be/uFE7qGA0mg4 Pretty sure this is backwards compatible.... Reviewed By: thechefchen Differential Revision: D4833557 fbshipit-source-id: 685af46243ba13246bf280dae8a4223381ce8cea
This commit is contained in:
parent
926bfdb9f4
commit
f25df504ed
|
@ -40,6 +40,7 @@ const {
|
|||
FooterComponent,
|
||||
HeaderComponent,
|
||||
ItemComponent,
|
||||
ItemSeparatorComponent,
|
||||
PlainInput,
|
||||
SeparatorComponent,
|
||||
Spindicator,
|
||||
|
@ -103,54 +104,59 @@ class FlatListExample extends React.PureComponent {
|
|||
<UIExplorerPage
|
||||
noSpacer={true}
|
||||
noScroll={true}>
|
||||
<View style={styles.searchRow}>
|
||||
<View style={styles.options}>
|
||||
<PlainInput
|
||||
onChangeText={this._onChangeFilterText}
|
||||
placeholder="Search..."
|
||||
value={this.state.filterText}
|
||||
/>
|
||||
<PlainInput
|
||||
onChangeText={this._onChangeScrollToIndex}
|
||||
placeholder="scrollToIndex..."
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.options}>
|
||||
{renderSmallSwitchOption(this, 'virtualized')}
|
||||
{renderSmallSwitchOption(this, 'horizontal')}
|
||||
{renderSmallSwitchOption(this, 'fixedHeight')}
|
||||
{renderSmallSwitchOption(this, 'logViewable')}
|
||||
{renderSmallSwitchOption(this, 'debug')}
|
||||
<Spindicator value={this._scrollPos} />
|
||||
<View style={styles.container}>
|
||||
<View style={styles.searchRow}>
|
||||
<View style={styles.options}>
|
||||
<PlainInput
|
||||
onChangeText={this._onChangeFilterText}
|
||||
placeholder="Search..."
|
||||
value={this.state.filterText}
|
||||
/>
|
||||
<PlainInput
|
||||
onChangeText={this._onChangeScrollToIndex}
|
||||
placeholder="scrollToIndex..."
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.options}>
|
||||
{renderSmallSwitchOption(this, 'virtualized')}
|
||||
{renderSmallSwitchOption(this, 'horizontal')}
|
||||
{renderSmallSwitchOption(this, 'fixedHeight')}
|
||||
{renderSmallSwitchOption(this, 'logViewable')}
|
||||
{renderSmallSwitchOption(this, 'debug')}
|
||||
<Spindicator value={this._scrollPos} />
|
||||
</View>
|
||||
</View>
|
||||
<SeparatorComponent />
|
||||
<AnimatedFlatList
|
||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
||||
ListHeaderComponent={<HeaderComponent />}
|
||||
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}
|
||||
/>
|
||||
</View>
|
||||
<SeparatorComponent />
|
||||
<AnimatedFlatList
|
||||
ItemSeparatorComponent={SeparatorComponent}
|
||||
ListHeaderComponent={<HeaderComponent />}
|
||||
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}
|
||||
/>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<ItemComponent
|
||||
item={item}
|
||||
horizontal={this.state.horizontal}
|
||||
fixedHeight={this.state.fixedHeight}
|
||||
onPress={this._pressItem}
|
||||
onShowUnderlay={separators.highlight}
|
||||
onHideUnderlay={separators.unhighlight}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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',
|
||||
|
|
|
@ -54,6 +54,7 @@ function genItemData(count: number, start: number = 0): Array<Item> {
|
|||
}
|
||||
|
||||
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 (
|
||||
<TouchableHighlight
|
||||
onPress={this._onPress}
|
||||
onShowUnderlay={this.props.onShowUnderlay}
|
||||
onHideUnderlay={this.props.onHideUnderlay}
|
||||
style={horizontal ? styles.horizItem : styles.item}>
|
||||
<View style={[
|
||||
styles.row, horizontal && {width: HORIZ_WIDTH}]}>
|
||||
styles.row, horizontal && {width: HORIZ_WIDTH}, fixedHeight && {height: ITEM_HEIGHT}]}>
|
||||
{!item.noImage && <Image style={styles.thumb} source={imgSource} />}
|
||||
<Text
|
||||
style={styles.text}
|
||||
|
@ -101,7 +106,7 @@ const renderStackedItem = ({item}: {item: Item}) => {
|
|||
class FooterComponent extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.headerFooterContainer}>
|
||||
<SeparatorComponent />
|
||||
<View style={styles.headerFooter}>
|
||||
<Text>LIST FOOTER</Text>
|
||||
|
@ -114,7 +119,7 @@ class FooterComponent extends React.PureComponent {
|
|||
class HeaderComponent extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.headerFooterContainer}>
|
||||
<View style={styles.headerFooter}>
|
||||
<Text>LIST HEADER</Text>
|
||||
</View>
|
||||
|
@ -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 <View style={style} />;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -97,7 +97,6 @@ class MultiColumnExample extends React.PureComponent {
|
|||
</View>
|
||||
<SeparatorComponent />
|
||||
<FlatList
|
||||
ItemSeparatorComponent={SeparatorComponent}
|
||||
ListFooterComponent={FooterComponent}
|
||||
ListHeaderComponent={HeaderComponent}
|
||||
getItemLayout={this.state.fixedHeight ? this._getItemLayout : undefined}
|
||||
|
@ -115,15 +114,18 @@ class MultiColumnExample extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
_getItemLayout(data: any, index: number): {length: number, offset: number, index: number} {
|
||||
return getItemLayout(data, index);
|
||||
const length = getItemLayout(data, index).length + 2 * (CARD_MARGIN + BORDER_WIDTH);
|
||||
return {length, offset: length * index, index};
|
||||
}
|
||||
_renderItemComponent = ({item}) => {
|
||||
return (
|
||||
<ItemComponent
|
||||
item={item}
|
||||
fixedHeight={this.state.fixedHeight}
|
||||
onPress={this._pressItem}
|
||||
/>
|
||||
<View style={styles.card}>
|
||||
<ItemComponent
|
||||
item={item}
|
||||
fixedHeight={this.state.fixedHeight}
|
||||
onPress={this._pressItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
// 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',
|
||||
|
|
|
@ -24,19 +24,40 @@ import type {Props as VirtualizedListProps} from 'VirtualizedList';
|
|||
|
||||
type RequiredProps<ItemT> = {
|
||||
/**
|
||||
* 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}) => (
|
||||
* <TouchableOpacity onPress={() => this._onPress(item)}>
|
||||
* <Text>{item.title}}</Text>
|
||||
* </TouchableOpacity>
|
||||
* );
|
||||
* ...
|
||||
* <FlatList data={[{title: 'Title Text', key: 'item1'}]} renderItem={this._renderItem} />
|
||||
* <FlatList
|
||||
* ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
|
||||
* <View style={[style.separator, highlighted && {marginLeft: 0}]} />
|
||||
* )}
|
||||
* data={[{title: 'Title Text', key: 'item1'}]}
|
||||
* renderItem={({item, separators}) => (
|
||||
* <TouchableHighlight
|
||||
* onPress={() => this._onPress(item)}
|
||||
* onShowUnderlay={separators.highlight}
|
||||
* onHideUnderlay={separators.unhighlight}>
|
||||
* <View style={{backgroundColor: 'white'}}>
|
||||
* <Text>{item.title}}</Text>
|
||||
* </View>
|
||||
* </TouchableHighlight>
|
||||
* )}
|
||||
* />
|
||||
*
|
||||
* 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<any>,
|
||||
renderItem: (info: {
|
||||
item: ItemT,
|
||||
index: number,
|
||||
separators: {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
},
|
||||
}) => ?React.Element<any>,
|
||||
/**
|
||||
* 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<ItemT> = {
|
|||
};
|
||||
type OptionalProps<ItemT> = {
|
||||
/**
|
||||
* 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<any>,
|
||||
/**
|
||||
|
@ -424,7 +448,7 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
|||
}
|
||||
};
|
||||
|
||||
_renderItem = (info: {item: ItemT | Array<ItemT>, index: number}) => {
|
||||
_renderItem = (info: Object) => {
|
||||
const {renderItem, numColumns, columnWrapperStyle} = this.props;
|
||||
if (numColumns > 1) {
|
||||
const {item, index} = info;
|
||||
|
@ -432,7 +456,11 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
|||
return (
|
||||
<View style={[{flexDirection: 'row'}, columnWrapperStyle]}>
|
||||
{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});
|
||||
})}
|
||||
</View>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -29,7 +29,15 @@ import type {ViewabilityConfig, ViewToken} from 'ViewabilityHelper';
|
|||
|
||||
type Item = any;
|
||||
|
||||
type renderItemType = (info: {item: Item, index: number}) => ?React.Element<any>;
|
||||
type renderItemType = (info: {
|
||||
item: Item,
|
||||
index: number,
|
||||
separators: {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
},
|
||||
}) => ?React.Element<any>;
|
||||
|
||||
type RequiredProps = {
|
||||
renderItem: renderItemType,
|
||||
|
@ -290,13 +298,10 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
|
|||
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<OptionalProps, Props, State> {
|
|||
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<OptionalProps, Props, State> {
|
|||
cells.push(
|
||||
<CellRenderer
|
||||
cellKey={key}
|
||||
ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
|
||||
index={ii}
|
||||
item={item}
|
||||
key={key}
|
||||
prevCellKey={prevCellKey}
|
||||
onUpdateSeparators={this._onUpdateSeparators}
|
||||
onLayout={(e) => this._onCellLayout(e, key, ii)}
|
||||
onUnmount={this._onCellUnmount}
|
||||
parentProps={this.props}
|
||||
ref={(ref) => {this._cellRefs[key] = ref;}}
|
||||
/>
|
||||
);
|
||||
if (ItemSeparatorComponent && ii < end) {
|
||||
cells.push(<ItemSeparatorComponent key={'sep' + key}/>);
|
||||
}
|
||||
prevCellKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdateSeparators = (keys: Array<?string>, 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<OptionalProps, Props, State> {
|
|||
}
|
||||
|
||||
_averageCellLength = 0;
|
||||
_cellRefs = {};
|
||||
_hasDataChangedSinceEndReached = true;
|
||||
_hasWarned = {};
|
||||
_highestMeasuredFrameIndex = 0;
|
||||
|
@ -776,34 +792,70 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
|
|||
|
||||
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<?string>, 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 (
|
||||
<View onLayout={this.props.onLayout}>
|
||||
<View onLayout={onLayout}>
|
||||
{element}
|
||||
{ItemSeparatorComponent && <ItemSeparatorComponent {...this.state.separatorProps} />}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -63,54 +63,66 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
|||
<header />
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
onLayout={undefined}
|
||||
>
|
||||
<item
|
||||
value="0"
|
||||
/>
|
||||
<item
|
||||
value="1"
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="0"
|
||||
/>
|
||||
<item
|
||||
value="1"
|
||||
/>
|
||||
</View>
|
||||
<separator />
|
||||
</View>
|
||||
<separator />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
onLayout={undefined}
|
||||
>
|
||||
<item
|
||||
value="2"
|
||||
/>
|
||||
<item
|
||||
value="3"
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="2"
|
||||
/>
|
||||
<item
|
||||
value="3"
|
||||
/>
|
||||
</View>
|
||||
<separator />
|
||||
</View>
|
||||
<separator />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
onLayout={undefined}
|
||||
>
|
||||
<item
|
||||
value="4"
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="4"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
|
|
Loading…
Reference in New Issue