react-native-draggablelist
Version:
288 lines (258 loc) • 9.83 kB
JavaScript
'use strict';
import React , {Component, PropTypes} from 'react'
import {
View,
ScrollView,
LayoutAnimation,
StyleSheet,
Animated,
} from 'react-native';
var AnimatedCell = require('./animatedCell');
var invariant = require('invariant');
var TimerMixin = require('react-timer-mixin');
var _ = require('lodash');
var DragableList = React.createClass({
mixins: [TimerMixin],
propTypes: {
dataSource: PropTypes.array.isRequired, //data
component: PropTypes.func.isRequired, //cell
cellProps: PropTypes.object, //cell props
shouldUpdateId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), //需要更新的cell id
onMove: PropTypes.func, //callback of move
keys: PropTypes.array, //pre orders of data
onPressCell: PropTypes.func, //
scrollStyle: View.propTypes.style, //scroll view style
contentInset: PropTypes.object, //scroll view contentInset
isScrollView: PropTypes.bool, //scrollView or view
toggleScroll: PropTypes.func, //if isScrollView is false, and outside component is a scrollView, should set this
shouldUpdate: PropTypes.bool, //update all cell
},
getDefaultProps() {
return {
dataSource: [],
isScrollView: true,
};
},
getInitialState() {
var keys = [];
var keyGroups = {};
var dataSource = this.props.dataSource || [];
if (this.props.keys && this.props.keys.length > 0) {
keys = this.props.keys;
for (var i = 0; i < dataSource.length; i++) {
var item = dataSource[i];
var key = item.id.toString();
keyGroups[key] = item;
}
}else{
for (var i = 0; i < dataSource.length; i++) {
var item = dataSource[i];
var key = item.id.toString();
keys[i] = key;
keyGroups[key] = item;
}
}
invariant(keys.length == this.props.dataSource.length, 'dataSource length should be equal to keys length');
return {
keys: keys,
key_groups: keyGroups,
restLayouts: [],
scrollable: true,
shouldUpdate: false,
}
},
setKeyGroups(dataSource) {
var keyGroups = {};
for (var i = 0; i < dataSource.length; i++) {
var item = dataSource[i];
var key = item.id.toString();
keyGroups[key] = item;
}
return keyGroups;
},
componentWillReceiveProps(nextProps) {
var {dataSource} = nextProps;
if (dataSource) {
var key_groups = this.setKeyGroups(dataSource);
if (this.state.keys.length > dataSource.length) { //delete some data
var newKeys = _.map(dataSource, (item) => {
return item.id.toString();
});
newKeys = _.intersection(this.state.keys, newKeys);
this.setState({
keys: newKeys,
key_groups,
})
}else if (this.state.keys.length < dataSource.length){ //add some data
var newKeys = _.map(dataSource, (item) => {
return item.id.toString();
});
newKeys = _.union(this.state.keys, newKeys);
this.setState({
keys: newKeys,
key_groups,
})
}
}
},
setTimeoutId: null,
//animate
_onMove(position: Point): void {
var newKeys = moveToClosest(this.state, position);
if (newKeys !== this.state.keys) {
LayoutAnimation.easeInEaseOut();
this.setState({keys: newKeys});
this.props.onMove && this.props.onMove(newKeys);
}
},
toggleScroll: function (can, callback) {
if (this.props.isScrollView) {
this.setState({
scrollable: can
}, callback);
}
this.props.toggleScroll && this.props.toggleScroll(can, callback)
},
render() {
var content = <View />;
var CellComponent = this.props.component;
var cellProps = this.props.cellProps;
var shouldUpdateId = null;
if (this.props.shouldUpdateId !== null && this.props.shouldUpdateId !== undefined) {
shouldUpdateId = this.props.shouldUpdateId.toString();
}
if (this.state.keys.length > 0) {
content = this.state.keys.map((key, idx) => {
if (key === null || key === undefined) return;
var row_data = this.state.key_groups[key];
var shouldUpdate = this.props.shouldUpdate || this.state.shouldUpdate || (shouldUpdateId == key);
if (key == this.state.activeKey) {
return (
<AnimatedCell
key={key + 'd'}
dummy={true}
cellProps={cellProps}
cellComponent={this.props.component}
rowData={row_data}
onPressCell={this.props.onPressCell}
shouldUpdate={shouldUpdate}
shouldUpdateId={this.props.shouldUpdateId}
/>
);
} else {
if (!this.state.restLayouts[idx]) {
var onLayout = function(index, e) {
var layout = e.nativeEvent.layout;
this.setState((state) => {
state.restLayouts[index] = layout;
return state;
});
}.bind(this, idx);
}
return (
<AnimatedCell
key={key}
onLayout={onLayout}
restLayout={this.state.restLayouts[idx]}
onActivate={() => {
this.setState({
shouldUpdate: true,
activeKey: key,
activeInitialLayout: this.state.restLayouts[idx],
})
}}
toggleScroll={this.toggleScroll}
cellProps={cellProps}
cellComponent={this.props.component}
rowData={row_data}
onPressCell={this.props.onPressCell}
shouldUpdate={shouldUpdate}
shouldUpdateId={this.props.shouldUpdateId}
/>
);
}
});
if (this.state.activeKey) {
var row_data = this.state.key_groups[this.state.activeKey];
var shouldUpdate = this.state.shouldUpdate || (shouldUpdateId == this.state.activeKey);
content.push(
<Animated.View key="dark" style={[styles.darkening]} />
);
content.push(
<AnimatedCell
key={this.state.activeKey}
restLayout={this.state.activeInitialLayout}
onMove={this._onMove}
onDeactivate={() => {
this.setState({
shouldUpdate: false,
activeKey: undefined
});
}}
toggleScroll={this.toggleScroll}
cellProps={cellProps}
cellComponent={this.props.component}
rowData={row_data}
onPressCell={this.props.onPressCell}
shouldUpdate={shouldUpdate}
shouldUpdateId={this.props.shouldUpdateId}
/>
);
}
}
var ViewTag = this.props.isScrollView ? ScrollView : View;
return (
<ViewTag
style={[{flex: 1}, this.props.scrollStyle]}
scrollEnabled={this.state.scrollable}
automaticallyAdjustContentInsets={false}
showsVerticalScrollIndicator={false}
contentInset={this.props.contentInset || {bottom: 0}}
>
{content}
</ViewTag>
)
}
});
function distance(p1, p2) {
if (!p1 || !p2) return 0;
var dx = p1.x - p2.x;
var dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
function moveToClosest({activeKey, keys, restLayouts}, position) {
var activeIdx = -1;
var closestIdx = activeIdx;
var minDist = Infinity;
var newKeys = [];
keys.forEach((key, idx) => {
var dist = distance(position, restLayouts[idx]);
if (key === activeKey) {
idx = activeIdx;
} else {
newKeys.push(key);
}
if (dist < minDist) {
minDist = dist;
closestIdx = idx;
}
});
if (closestIdx === activeIdx) {
return keys;
} else {
newKeys.splice(closestIdx, 0, activeKey);
return newKeys;
}
}
var styles = StyleSheet.create({
darkening: {
backgroundColor: 'black',
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
opacity: 0,
},
});
module.exports = DragableList;