mirror of
https://github.com/status-im/react-native.git
synced 2025-02-11 17:07:09 +00:00
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
160 lines
5.1 KiB
JavaScript
160 lines
5.1 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule MetroListView
|
|
* @flow
|
|
*/
|
|
'use strict';
|
|
|
|
const ListView = require('ListView');
|
|
const React = require('React');
|
|
const RefreshControl = require('RefreshControl');
|
|
const ScrollView = require('ScrollView');
|
|
|
|
const invariant = require('fbjs/lib/invariant');
|
|
|
|
type Item = any;
|
|
|
|
type NormalProps = {
|
|
FooterComponent?: ReactClass<*>,
|
|
renderItem: (info: Object) => ?React.Element<*>,
|
|
renderSectionHeader?: ({section: Object}) => ?React.Element<*>,
|
|
SeparatorComponent?: ?ReactClass<*>, // not supported yet
|
|
|
|
// Provide either `items` or `sections`
|
|
items?: ?Array<Item>, // By default, an Item is assumed to be {key: string}
|
|
sections?: ?Array<{key: string, data: Array<Item>}>,
|
|
|
|
/**
|
|
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
|
|
* sure to also set the `refreshing` prop correctly.
|
|
*/
|
|
onRefresh?: ?Function,
|
|
/**
|
|
* Set this true while waiting for new data from a refresh.
|
|
*/
|
|
refreshing?: boolean,
|
|
};
|
|
type DefaultProps = {
|
|
keyExtractor: (item: Item) => string,
|
|
};
|
|
type Props = NormalProps & DefaultProps;
|
|
|
|
/**
|
|
* This is just a wrapper around the legacy ListView that matches the new API of FlatList, but with
|
|
* some section support tacked on. It is recommended to just use FlatList directly, this component
|
|
* is mostly for debugging and performance comparison.
|
|
*/
|
|
class MetroListView extends React.Component {
|
|
props: Props;
|
|
scrollToEnd(params?: ?{animated?: ?boolean}) {
|
|
throw new Error('scrollToEnd not supported in legacy ListView.');
|
|
}
|
|
scrollToIndex(params: {animated?: ?boolean, index: number, viewPosition?: number}) {
|
|
throw new Error('scrollToIndex not supported in legacy ListView.');
|
|
}
|
|
scrollToItem(params: {animated?: ?boolean, item: Item, viewPosition?: number}) {
|
|
throw new Error('scrollToItem not supported in legacy ListView.');
|
|
}
|
|
scrollToLocation() {
|
|
throw new Error('scrollToLocation not supported in legacy ListView.');
|
|
}
|
|
scrollToOffset(params: {animated?: ?boolean, offset: number}) {
|
|
const {animated, offset} = params;
|
|
this._listRef.scrollTo(
|
|
this.props.horizontal ? {x: offset, animated} : {y: offset, animated}
|
|
);
|
|
}
|
|
getListRef() {
|
|
return this._listRef;
|
|
}
|
|
static defaultProps: DefaultProps = {
|
|
keyExtractor: (item, index) => item.key || index,
|
|
renderScrollComponent: (props: Props) => {
|
|
if (props.onRefresh) {
|
|
return (
|
|
<ScrollView
|
|
{...props}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={props.refreshing}
|
|
onRefresh={props.onRefresh}
|
|
/>
|
|
}
|
|
/>
|
|
);
|
|
} else {
|
|
return <ScrollView {...props} />;
|
|
}
|
|
},
|
|
};
|
|
state = this._computeState(
|
|
this.props,
|
|
{
|
|
ds: new ListView.DataSource({
|
|
rowHasChanged: (itemA, itemB) => true,
|
|
sectionHeaderHasChanged: () => true,
|
|
getSectionHeaderData: (dataBlob, sectionID) => this.state.sectionHeaderData[sectionID],
|
|
}),
|
|
sectionHeaderData: {},
|
|
},
|
|
);
|
|
componentWillReceiveProps(newProps: Props) {
|
|
this.setState((state) => this._computeState(newProps, state));
|
|
}
|
|
render() {
|
|
return (
|
|
<ListView
|
|
{...this.props}
|
|
dataSource={this.state.ds}
|
|
ref={this._captureRef}
|
|
renderRow={this._renderRow}
|
|
renderFooter={this.props.FooterComponent && this._renderFooter}
|
|
renderSectionHeader={this.props.sections && this._renderSectionHeader}
|
|
renderSeparator={this.props.SeparatorComponent && this._renderSeparator}
|
|
/>
|
|
);
|
|
}
|
|
_listRef: ListView;
|
|
_captureRef = (ref) => { this._listRef = ref; };
|
|
_computeState(props: Props, state) {
|
|
const sectionHeaderData = {};
|
|
if (props.sections) {
|
|
invariant(!props.items, 'Cannot have both sections and items props.');
|
|
const sections = {};
|
|
props.sections.forEach((sectionIn, ii) => {
|
|
const sectionID = 's' + ii;
|
|
sections[sectionID] = sectionIn.data;
|
|
sectionHeaderData[sectionID] = sectionIn;
|
|
});
|
|
return {
|
|
ds: state.ds.cloneWithRowsAndSections(sections),
|
|
sectionHeaderData,
|
|
};
|
|
} else {
|
|
invariant(!props.sections, 'Cannot have both sections and items props.');
|
|
return {
|
|
ds: state.ds.cloneWithRows(props.items),
|
|
sectionHeaderData,
|
|
};
|
|
}
|
|
}
|
|
_renderFooter = () => <this.props.FooterComponent key="$footer" />;
|
|
_renderRow = (item, sectionID, rowID, highlightRow) => {
|
|
return this.props.renderItem({item, index: rowID});
|
|
};
|
|
_renderSectionHeader = (section, sectionID) => {
|
|
const {renderSectionHeader} = this.props;
|
|
invariant(renderSectionHeader, 'Must provide renderSectionHeader with sections prop');
|
|
return renderSectionHeader({section});
|
|
}
|
|
_renderSeparator = (sID, rID) => <this.props.SeparatorComponent key={sID + rID} />;
|
|
}
|
|
|
|
module.exports = MetroListView;
|