|
2 | 2 |
|
3 | 3 | import React, { PureComponent } from 'react';
|
4 | 4 | import PropTypes from 'prop-types';
|
5 |
| -import { Dimensions, ListView, StyleSheet, ViewPropTypes } from 'react-native'; |
| 5 | +import { |
| 6 | + Animated, |
| 7 | + StyleSheet, |
| 8 | + VirtualizedList, |
| 9 | + ViewPropTypes, |
| 10 | +} from 'react-native'; |
6 | 11 | import withTheme from '../core/withTheme';
|
7 | 12 | import type { Theme } from '../types/Theme';
|
8 | 13 |
|
9 |
| -type Layout = { |
10 |
| - width: number, |
11 |
| -}; |
12 |
| - |
13 | 14 | type Props = {
|
14 |
| - dataSource: ListView.DataSource, |
| 15 | + /** |
| 16 | + * Item's spacing |
| 17 | + */ |
15 | 18 | spacing: number,
|
| 19 | + /** |
| 20 | + * Function which determine number of columns. |
| 21 | + */ |
16 | 22 | getNumberOfColumns: (width: number) => number,
|
17 |
| - renderSectionHeader?: (...args: any) => any, |
18 |
| - renderRow: (...args: any) => any, |
19 |
| - initialLayout: Layout, |
| 23 | + /** |
| 24 | + * Data for the list |
| 25 | + */ |
| 26 | + data: Array<any>, |
| 27 | + /** |
| 28 | + * Function which should return ID base on the item. |
| 29 | + */ |
| 30 | + keyExtractor: (item: any) => string, |
| 31 | + contentContainerStyle: ?Object, |
| 32 | + /** |
| 33 | + * Component for rendering item |
| 34 | + */ |
| 35 | + renderItem: (item: any) => React$Element<*>, |
20 | 36 | onLayout?: Function,
|
21 |
| - contentContainerStyle?: any, |
22 | 37 | theme: Theme,
|
23 | 38 | };
|
24 | 39 |
|
25 | 40 | type DefaultProps = {
|
26 |
| - initialLayout: Layout, |
27 | 41 | getNumberOfColumns: (width: number) => number,
|
28 | 42 | spacing: number,
|
29 | 43 | };
|
30 | 44 |
|
31 | 45 | type State = {
|
32 |
| - layout: Layout, |
| 46 | + itemWidth: Animated.Value, |
33 | 47 | };
|
34 | 48 |
|
35 | 49 | class GridView extends PureComponent<DefaultProps, Props, State> {
|
36 | 50 | static propTypes = {
|
37 |
| - dataSource: PropTypes.instanceOf(ListView.DataSource).isRequired, |
| 51 | + data: PropTypes.array.isRequired, |
38 | 52 | spacing: PropTypes.number.isRequired,
|
39 | 53 | getNumberOfColumns: PropTypes.func.isRequired,
|
40 |
| - renderSectionHeader: PropTypes.func, |
41 |
| - renderRow: PropTypes.func.isRequired, |
| 54 | + renderItem: PropTypes.func.isRequired, |
| 55 | + keyExtractor: PropTypes.func.isRequired, |
42 | 56 | onLayout: PropTypes.func,
|
43 | 57 | theme: PropTypes.object.isRequired,
|
44 | 58 | contentContainerStyle: ViewPropTypes.style,
|
45 | 59 | };
|
46 | 60 |
|
47 | 61 | static defaultProps = {
|
48 |
| - initialLayout: { width: Dimensions.get('window').width }, |
49 | 62 | getNumberOfColumns: () => 1,
|
50 | 63 | spacing: 0,
|
51 | 64 | };
|
52 | 65 |
|
53 |
| - static DataSource = ListView.DataSource; |
54 |
| - |
55 | 66 | constructor(props: Props) {
|
56 | 67 | super(props);
|
57 | 68 |
|
58 | 69 | this.state = {
|
59 |
| - layout: props.initialLayout, |
| 70 | + itemWidth: new Animated.Value(0), |
60 | 71 | };
|
61 | 72 | }
|
62 | 73 |
|
63 | 74 | state: State;
|
64 | 75 |
|
65 |
| - scrollTo(options: any) { |
66 |
| - this._root.scrollTo(options); |
67 |
| - } |
| 76 | + _root: VirtualizedList; |
68 | 77 |
|
69 |
| - _root: Object; |
| 78 | + scrollToIndex = (params: Object) => this._root.scrollToIndex(params); |
| 79 | + scrollToItem = (params: Object) => this._root.scrollToItem(params); |
| 80 | + scrollToEnd = (params?: Object) => this._root.scrollToEnd(params); |
| 81 | + scrollToOffset = (params: Object) => this._root.scrollToOffset(params); |
70 | 82 |
|
71 |
| - _renderSectionHeader = (...args) => { |
72 |
| - const header = this.props.renderSectionHeader |
73 |
| - ? this.props.renderSectionHeader(...args) |
74 |
| - : null; |
75 |
| - if (!header) { |
76 |
| - return header; |
77 |
| - } |
78 |
| - const { width } = this.state.layout; |
79 |
| - return React.cloneElement(header, { |
80 |
| - style: [header.props.style, { width }], |
81 |
| - }); |
82 |
| - }; |
| 83 | + _renderItem = ({ item }) => { |
| 84 | + const { spacing, renderItem } = this.props; |
83 | 85 |
|
84 |
| - _renderRow = (...args: any) => { |
85 |
| - const containerWidth = this.state.layout.width; |
86 |
| - const { getNumberOfColumns, spacing } = this.props; |
87 | 86 | const style = {
|
88 |
| - width: |
89 |
| - (containerWidth - spacing) / getNumberOfColumns(containerWidth) - |
90 |
| - spacing, |
| 87 | + width: this.state.itemWidth, |
91 | 88 | margin: spacing / 2,
|
92 | 89 | };
|
93 |
| - const row = this.props.renderRow(...args); |
94 |
| - if (!row) { |
95 |
| - return row; |
96 |
| - } |
97 |
| - return React.cloneElement(row, { |
98 |
| - style: [row.props.style, style], |
99 |
| - }); |
| 90 | + |
| 91 | + return <Animated.View style={style}>{renderItem(item)}</Animated.View>; |
100 | 92 | };
|
101 | 93 |
|
102 | 94 | _handleLayout = (e: any) => {
|
| 95 | + const { getNumberOfColumns, spacing } = this.props; |
| 96 | + |
103 | 97 | if (this.props.onLayout) {
|
104 | 98 | this.props.onLayout(e);
|
105 | 99 | }
|
106 | 100 |
|
107 |
| - if (this.state.layout.width === e.nativeEvent.layout.width) { |
108 |
| - return; |
109 |
| - } |
| 101 | + const layoutWidth = e.nativeEvent.layout.width; |
110 | 102 |
|
111 |
| - const containerWidth = e.nativeEvent.layout.width; |
112 |
| - |
113 |
| - this.setState({ |
114 |
| - layout: { width: containerWidth }, |
115 |
| - }); |
| 103 | + this.state.itemWidth.setValue( |
| 104 | + (layoutWidth - spacing) / getNumberOfColumns(layoutWidth) - spacing |
| 105 | + ); |
116 | 106 | };
|
117 | 107 |
|
118 |
| - _setRef = (c: Object) => (this._root = c); |
| 108 | + _getItemCount = (data: Array<any>) => data.length; |
| 109 | + |
| 110 | + _getItem = (data, index) => data[index]; |
119 | 111 |
|
120 | 112 | render() {
|
121 |
| - const { spacing, theme } = this.props; |
| 113 | + const { spacing, theme, data, keyExtractor } = this.props; |
122 | 114 | return (
|
123 |
| - <ListView |
| 115 | + <VirtualizedList |
124 | 116 | {...this.props}
|
125 |
| - key={`grid-${this.state.layout.width}`} |
| 117 | + data={data} |
| 118 | + getItemCount={this._getItemCount} |
| 119 | + getItem={this._getItem} |
126 | 120 | onLayout={this._handleLayout}
|
127 |
| - renderSectionHeader={this._renderSectionHeader} |
128 |
| - renderRow={this._renderRow} |
| 121 | + renderItem={this._renderItem} |
| 122 | + keyExtractor={keyExtractor} |
| 123 | + ref={c => (this._root = c)} |
129 | 124 | contentContainerStyle={[
|
130 | 125 | styles.grid,
|
131 | 126 | { padding: spacing / 2, backgroundColor: theme.colors.background },
|
132 | 127 | this.props.contentContainerStyle,
|
133 | 128 | ]}
|
134 |
| - ref={this._setRef} |
135 | 129 | />
|
136 | 130 | );
|
137 | 131 | }
|
138 | 132 | }
|
139 | 133 |
|
140 | 134 | const styles = StyleSheet.create({
|
141 | 135 | grid: {
|
| 136 | + flexWrap: 'wrap', // Warning is misleading https://github.com/facebook/react-native/issues/15772 |
142 | 137 | flexDirection: 'row',
|
143 |
| - flexWrap: 'wrap', |
144 | 138 | alignItems: 'flex-start',
|
145 | 139 | },
|
146 | 140 | });
|
|
0 commit comments