Skip to content

Instantly share code, notes, and snippets.

@brentvatne
Forked from deanmcpherson/SortableListView.js
Created February 1, 2016 05:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brentvatne/6ad65c675c263b010f09 to your computer and use it in GitHub Desktop.
Save brentvatne/6ad65c675c263b010f09 to your computer and use it in GitHub Desktop.
React native drag and drop list view in progress
var React = require('react-native');
var {
ListView,
LayoutAnimation,
View,
Animated,
PanResponder,
TouchableWithoutFeedback
} = React;
var Row = React.createClass({
shouldComponentUpdate: function(props) {
if (props.hovering !== this.props.hovering) return true;
if (props.rowData.data !== this.props.rowData.data) return true;
return false;
},
handleLongPress: function(e) {
this.refs.view.measure((frameX, frameY, frameWidth, frameHeight, pageX, pageY) => {
let layout = {frameX, frameY, frameWidth, frameHeight, pageX, pageY};
this.props.onRowActive({
layout: layout,
touch: e.nativeEvent,
rowData: this.props.rowData
});
});
},
render: function() {
let layout = this.props.list.layoutMap[this.props.rowData.index];
let activeData = this.props.list.state.active;
let activeIndex = activeData ? Number(activeData.rowData.index) : -5;
let shouldDisplayHovering = !(activeIndex == this.props.rowData.index || activeIndex+1 == this.props.rowData.index);
let Row = React.cloneElement(this.props.renderRow(this.props.rowData.data, this.props.rowData.section, this.props.rowData.index, null, this.props.active), {onLongPress: this.handleLongPress});
return <View onLayout={this.props.onRowLayout} style={this.props.active ? {opacity: .3}: null} ref="view">
{this.props.hovering && shouldDisplayHovering ? this.props.activeDivider : null}
{Row}
</View>
}
});
var SortRow = React.createClass({
getInitialState: function() {
let layout = this.props.list.state.active.layout;
let rowLayout = this.props.list.layoutMap[this.props.rowData.index];
return {
style: {
position: 'absolute',
left: 0,
right: 0,
height: layout.frameHeight,
overflow: 'hidden',
backgroundColor: 'transparent',
marginTop: layout.pageY - 20 //Account for top bar spacing
}
}
},
render: function() {
let handlers = this.props.panResponder.panHandlers;
return <Animated.View style={[this.state.style, this.props.list.state.pan.getLayout()]}>
{this.props.renderRow(this.props.rowData.data, this.props.rowData.section, this.props.rowData.index, true)}
</Animated.View>
}
});
var SortableListView = React.createClass({
getInitialState:function() {
let currentPanValue = {x: 0, y: 0};
this.state = {
ds: new ListView.DataSource({rowHasChanged: (r1, r2) => {
let dataChanged = r1 == r2;
return true
}}),
sorting: false,
active: false,
pan: new Animated.ValueXY(currentPanValue)
};
let onPanResponderMoveCb = Animated.event([null, {
dx: this.state.pan.x, // x,y are Animated.Value
dy: this.state.pan.y,
}]);
this.state.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderMove: (evt, gestureState) => {
gestureState.dx = 0;
this.moveY = gestureState.moveY;
onPanResponderMoveCb(evt, gestureState);
},
onPanResponderGrant: (e, gestureState) => {
console.log('granted')
this.state.pan.setOffset(currentPanValue);
this.state.pan.setValue(currentPanValue);
},
onPanResponderReject: (e, gestureState) => {
console.log('rejected', e)
},
onPanResponderRelease: (e) => {
this.setState({active: false, sorting: false, hovering: false});
}
});
return this.state;
},
componentDidMount: function() {
this.scrollResponder = this.refs.list.getScrollResponder();
},
scrollValue: 0,
scrollAnimation: function() {
if (this.isMounted() && this.state.active) {
if (this.moveY == undefined) return requestAnimationFrame(this.scrollAnimation);
let SCROLL_LOWER_BOUND = 100;
let SCROLL_HIGHER_BOUND = this.listLayout.height - SCROLL_LOWER_BOUND;
let MAX_SCROLL_VALUE = this.scrollContainerHeight;
let currentScrollValue = this.scrollValue;
let newScrollValue = null;
let SCROLL_MAX_CHANGE = 15;
if (this.moveY < SCROLL_LOWER_BOUND && currentScrollValue > 0) {
let PERCENTAGE_CHANGE = 1 - (this.moveY / SCROLL_LOWER_BOUND);
newScrollValue = currentScrollValue - (PERCENTAGE_CHANGE * SCROLL_MAX_CHANGE);
if (newScrollValue < 0) newScrollValue = 0;
}
if (this.moveY > SCROLL_HIGHER_BOUND && currentScrollValue < MAX_SCROLL_VALUE) {
let PERCENTAGE_CHANGE = 1 - ((this.listLayout.height - this.moveY) / SCROLL_LOWER_BOUND);
newScrollValue = currentScrollValue + (PERCENTAGE_CHANGE * SCROLL_MAX_CHANGE);
if (newScrollValue > MAX_SCROLL_VALUE) newScrollValue = MAX_SCROLL_VALUE;
}
if (newScrollValue !== null) {
this.scrollValue = newScrollValue;
this.scrollResponder.scrollWithoutAnimationTo(this.scrollValue, 0);
}
this.checkTargetElement();
requestAnimationFrame(this.scrollAnimation);
}
},
checkTargetElement() {
let scrollValue = this.scrollValue;
let moveY = this.moveY;
let targetPixel = scrollValue + moveY;
let i = 0;
let x = 0;
let row;
while (i < targetPixel) {
row = this.layoutMap[x];
if (!row) {
return;
}
i += row.height;
x++;
}
x--;
if (x != this.state.hovering) {
LayoutAnimation.easeInEaseOut();
this.setState({
hovering: String(x)
})
}
},
layoutMap: {},
handleRowActive: function(row) {
this.state.pan.setValue({x: 0, y: 0});
LayoutAnimation.easeInEaseOut();
this.setState({
sorting: true,
active: row
}, this.scrollAnimation);
},
renderActiveDivider: function() {
if (this.props.activeDivider) this.props.activeDivider();
return <View style={{height: this.state.active && this.state.active.layout.frameHeight}} />
},
renderRow: function(data, section, index, highlightfn, active) {
let Component = active ? SortRow : Row;
let isActiveRow = (!active && this.state.active && this.state.active.rowData.index === index);
if (!active && isActiveRow) {
active = {active: true};
}
return <Component
{...this.props}
activeDivider={this.renderActiveDivider() }
key={index}
active={active}
list={this}
hovering={this.state.hovering === index}
panResponder={this.state.panResponder}
rowData={{data, section, index}}
onRowActive={this.handleRowActive}
onRowLayout={layout => this.layoutMap[index] = layout.nativeEvent.layout}
/>
},
renderActive: function() {
if (!this.state.active) return;
let index = this.state.active.rowData.index;
return this.renderRow(this.props.data[index], 's1', index, () => {}, {active: true, thumb: true});
},
render: function() {
global.list = this;
let dataSource = this.state.ds.cloneWithRows(this.props.data);
return <View style={{flex: 1}}>
<ListView
{...this.props}
{...this.state.panResponder.panHandlers}
ref="list"
dataSource={dataSource}
onScroll={e => {
this.scrollValue = e.nativeEvent.contentOffset.y;
this.scrollContainerHeight = e.nativeEvent.contentSize.height;
}}
onLayout={(e) => this.listLayout = e.nativeEvent.layout}
removeClippedSubviews={false}
scrollEnabled={!this.state.active}
renderRow={this.renderRow}
/>
{this.renderActive()}
</View>
}
});
module.exports = SortableListView;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment