react-native-gridview
Version:
A flexible grid view based on React Native's ListView component.
195 lines (170 loc) • 5.21 kB
JavaScript
import React, {
Component,
} from 'react';
import PropTypes from 'prop-types';
import {
Dimensions,
ListView,
StyleSheet,
View,
} from 'react-native';
class GridView extends Component {
constructor(props) {
super(props);
this._getDataSource = this._getDataSource.bind(this);
this._initialLayout = true;
const dataSource = this._getDataSource(props);
this.state = { dataSource };
}
static get DataSource() {
return ListView.DataSource;
}
static get propTypes() {
return {
data: PropTypes.array,
fillMissingItems: PropTypes.bool,
itemsPerRow: PropTypes.number,
itemsPerRowLandscape: PropTypes.number,
itemsPerRowPortrait: PropTypes.number,
itemStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.number,
PropTypes.array,
]),
renderItem: PropTypes.func.isRequired,
rowStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.number,
PropTypes.array,
]),
};
}
static get defaultProps() {
return {
fillMissingItems: true,
itemsPerRow: 1,
itemsPerRowLandscape: null,
itemsPerRowPortrait: null,
};
}
componentWillReceiveProps(props) {
const dataSource = this._getDataSource(props);
this.setState({ dataSource });
}
render() {
if (!this.props.data && !this.props.dataSource)
throw new Error('GridView component requires "data" or "dataSource"');
const itemsPerRow = this.itemsPerRow;
return (
<ListView
onLayout={(...args) => {
/**
* The grid data needs to be rebound if the items per row is
* different between portrait and landscape modes. Only rebind
* after the first layout call, since we've already bound data
* in the initial state.
*/
if (this._initialLayout) {
this._initialLayout = false;
} else if (this.props.itemsPerRowLandscape || this.props.itemsPerRowPortrait) {
const dataSource = this._getDataSource(this.props);
this.setState({ dataSource });
}
if (this.props.onLayout)
this.props.onLayout(...args);
}}
enableEmptySections={true}
{...this.props}
dataSource={this.state.dataSource}
contentContainerStyle={[
styles.list,
this.props.contentContainerStyle,
]}
renderRow={(rowData, sectionID, rowID, ...args) => {
return (
<View style={[styles.row, this.props.rowStyle]}>
{rowData.map((item, index) => {
const itemID = rowID * itemsPerRow + index;
return (
<View key={itemID} style={[styles.item, this.props.itemStyle]}>
{
item
? this.props.renderItem(item, sectionID, rowID, index, itemID, ...args)
: null
}
</View>
);
})}
</View>
);
}}
/>
);
}
get itemsPerRow() {
let { itemsPerRow } = this.props;
const { itemsPerRowPortrait, itemsPerRowLandscape } = this.props;
if (itemsPerRowPortrait || itemsPerRowLandscape) {
const { width, height } = Dimensions.get('window');
const isLandscape = width > height;
if (isLandscape && itemsPerRowLandscape)
itemsPerRow = itemsPerRowLandscape;
else if (!isLandscape && itemsPerRowPortrait)
itemsPerRow = itemsPerRowPortrait;
}
return itemsPerRow;
}
_getDataSource(props) {
let { dataSource } = props;
if (!dataSource) {
const { data } = props;
const hasHeaders = !Array.isArray(data);
if (hasHeaders) {
const rowData = Object.keys(data)
.reduce((value, prop) => {
value[prop] = this._getRowData(data[prop]);
return value;
}, {});
dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
}).cloneWithRowsAndSections(rowData);
} else {
const rowData = this._getRowData(data);
dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
}).cloneWithRows(rowData);
}
}
return dataSource;
}
_getRowData(data) {
const itemsPerRow = this.itemsPerRow;
const rowCount = Math.ceil(data.length / itemsPerRow);
const rowData = [];
for (let i = 0; i < rowCount; i++) {
const startIndex = i * itemsPerRow;
const endIndex = startIndex + itemsPerRow;
const items = data.slice(startIndex, endIndex);
if (this.props.fillMissingItems && items.length < itemsPerRow) {
const diff = itemsPerRow - items.length;
items.push(...Array(diff).fill(null));
}
rowData.push(items);
}
return rowData;
}
}
const styles = StyleSheet.create({
list: {
flexWrap: 'wrap',
alignItems: 'flex-start',
},
row: {
flexDirection: 'row',
},
item: {
flex: 1,
},
});
export default GridView;