Files
react-native/Examples/UIExplorer/js/MultiColumnExample.js
Spencer Ahrens f25df504ed 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
2017-04-12 17:01:03 -07:00

169 lines
5.1 KiB
JavaScript

/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
* @providesModule MultiColumnExample
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {
FlatList,
StyleSheet,
Text,
View,
} = ReactNative;
const UIExplorerPage = require('./UIExplorerPage');
const infoLog = require('infoLog');
const {
FooterComponent,
HeaderComponent,
ItemComponent,
PlainInput,
SeparatorComponent,
genItemData,
getItemLayout,
pressItem,
renderSmallSwitchOption,
} = require('./ListExampleShared');
class MultiColumnExample extends React.PureComponent {
static title = '<FlatList> - MultiColumn';
static description = 'Performant, scrollable grid of data.';
state = {
data: genItemData(1000),
filterText: '',
fixedHeight: true,
logViewable: false,
numColumns: 2,
virtualized: true,
};
_onChangeFilterText = (filterText) => {
this.setState(() => ({filterText}));
};
_onChangeNumColumns = (numColumns) => {
this.setState(() => ({numColumns: Number(numColumns)}));
};
render() {
const filterRegex = new RegExp(String(this.state.filterText), 'i');
const filter = (item) => (filterRegex.test(item.text) || filterRegex.test(item.title));
const filteredData = this.state.data.filter(filter);
return (
<UIExplorerPage
title={this.props.navigator ? null : '<FlatList> - MultiColumn'}
noSpacer={true}
noScroll={true}>
<View style={styles.searchRow}>
<View style={styles.row}>
<PlainInput
onChangeText={this._onChangeFilterText}
placeholder="Search..."
value={this.state.filterText}
/>
<Text> numColumns: </Text>
<PlainInput
clearButtonMode="never"
onChangeText={this._onChangeNumColumns}
value={this.state.numColumns ? String(this.state.numColumns) : ''}
/>
</View>
<View style={styles.row}>
{renderSmallSwitchOption(this, 'virtualized')}
{renderSmallSwitchOption(this, 'fixedHeight')}
{renderSmallSwitchOption(this, 'logViewable')}
</View>
</View>
<SeparatorComponent />
<FlatList
ListFooterComponent={FooterComponent}
ListHeaderComponent={HeaderComponent}
getItemLayout={this.state.fixedHeight ? this._getItemLayout : undefined}
data={filteredData}
key={this.state.numColumns + (this.state.fixedHeight ? 'f' : 'v')}
numColumns={this.state.numColumns || 1}
onRefresh={() => alert('onRefresh: nothing to refresh :P')}
refreshing={false}
renderItem={this._renderItemComponent}
disableVirtualization={!this.state.virtualized}
onViewableItemsChanged={this._onViewableItemsChanged}
legacyImplementation={false}
/>
</UIExplorerPage>
);
}
_getItemLayout(data: any, index: number): {length: number, offset: number, index: number} {
const length = getItemLayout(data, index).length + 2 * (CARD_MARGIN + BORDER_WIDTH);
return {length, offset: length * index, index};
}
_renderItemComponent = ({item}) => {
return (
<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.
_onViewableItemsChanged = (info: {
changed: Array<{
key: string, isViewable: boolean, item: {columns: Array<*>}, index: ?number, section?: any
}>},
) => {
// Impressions can be logged here
if (this.state.logViewable) {
infoLog('onViewableItemsChanged: ', info.changed.map((v) => ({...v, item: '...'})));
}
};
_pressItem = (key: number) => {
pressItem(this, key);
};
}
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',
},
searchRow: {
padding: 10,
},
});
module.exports = MultiColumnExample;