react native实现拖拽/拖放
首先初始化一个react native的项目,cd到你的目录下运行
react-native init rndd
创建一个新的组件./app/components/Draggable.js
import React, {
Component,
} from 'react';
import {
StyleSheet,
Image,
PanResponder,
Animated,
} from 'react-native';
class Draggable extends Component{
constructor(props){
super(props);
}
render(){
return (
<Animated.View style={styles.container}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
}
export default Draggable;
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 100,
top: 100,
}
});
加上PanResponder
PanResponder的api可以上官网查看PanResponder
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
},
onPanResponderMove: Animated.event([
]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
render(){
return (
<Animated.View style={styles.container} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
响应拖拽事件
先在state中创建一个对象来记录拖拽的记录。
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY()
};
}
更新panHandler:
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
// 设置初始位置
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setValue({x: 0, y: 0});
},
// 使用拖拽的偏移量来定位
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
更新render方法
render(){
// 从state中取出pan
const { pan } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
这个时候我们刷新用CMD+R刷新,发现已经可以拖动这个图片了,但是有个问题,第二次拖拽的时候又从原点开始移动,我们接下来解决这个问题。
我们有几个选择:
- 让拖动的元素在释放的时候回到原来的位置:
修改PanResponder的onPanResponderRelease方法onPanResponderRelease: (e, {vx, vy}) => { Animated.spring( this.state.pan, {toValue: {x: 0, y: 0}} ).start(); }
- 让拖动的元素停留在拖拽到的位置:
修改PanResponder的onPanResponderGrant和onPanResponderRelease方法this._panResponder = PanResponder.create({ onMoveShouldSetResponderCapture: () => true, onMoveShouldSetPanResponderCapture: () => true, // 设置初始位置 onPanResponderGrant: (e, gestureState) => { this.state.pan.setOffset({ x: this.state.pan.x._value, y: this.state.pan.y._value }); this.state.pan.setValue({x: 0, y: 0}); }, // 使用拖拽的偏移量来定位 onPanResponderMove: Animated.event([ null, {dx: this.state.pan.x, dy: this.state.pan.y}, ]), onPanResponderRelease: (e, {vx, vy}) => { this.state.pan.flattenOffset(); } });
我们再来加上拖拽时候放大的效果
先在state里加上一个scale用来记录放大的倍数
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1)
};
我们要在render里使用放大的值
render(){
// 从state中取出pan和scale
const { pan, scale } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}, {scale}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
在拖拽的时候变大:
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value
});
this.state.pan.setValue({x: 0, y: 0});
Animated.spring(
this.state.scale,
{ toValue: 1.3, friction: 3 }
).start();
},
在释放的时候缩小到原来的样子:
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
Animated.spring(
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
}
加上拖拽的旋转效果:
先在state中加上一个rotate用来记录旋转的角度
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
rotate: new Animated.Value(0)
};
在拖拽的时候旋转,需要在onPanResponderGrant里加上
Animated.timing(this.state.rotate, {
toValue: 25, // 旋转25%,render里interpolate出deg的值
duration: 300
}).start();
在释放的时候恢复原状,需要在onPanResponderRelease中加上
Animated.timing(this.state.rotate, {
toValue: 0,
duration: 300
}).start();
render函数里设置transform:
render(){
// 从state中取出pan
const { pan, scale } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 计算旋转
const rotate = this.state.rotate.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg']
});
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}, {scale}, {rotate}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
完整代码:
https://github.com/shengoo/rndd
import React, {
Component,
} from 'react';
import {
StyleSheet,
Image,
PanResponder,
Animated,
} from 'react-native';
class Draggable extends Component{
constructor(props){
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
rotate: new Animated.Value(0)
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
// 设置初始位置
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value
});
this.state.pan.setValue({x: 0, y: 0});
Animated.spring(this.state.scale, {
toValue: 1.3,
friction: 3 }
).start();
Animated.timing(this.state.rotate, {
toValue: 25,
duration: 300
}).start();
},
// 使用拖拽的偏移量来定位
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
// Animated.spring(
// this.state.pan,
// {toValue: {x: 0, y: 0}}
// ).start();
Animated.spring(
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
Animated.timing(this.state.rotate, {
toValue: 0,
duration: 300
}).start();
}
});
}
render(){
// 从state中取出pan
const { pan, scale } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 计算旋转
const rotate = this.state.rotate.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg']
});
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}, {scale}, {rotate}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
}
export default Draggable;
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 100,
top: 100,
}
});