一、PanResponder相关知识点
PanResponder类可以将多点触摸操作协调成一个手势。它使得一个单点触摸可以接受更多的触摸操作,也可以用于识别简单的多点触摸手势。
默认情况下PanResponder会通过InteractionManager来阻止长时间运行的 JS 事件打断当前的手势活动。
它提供了一个对触摸响应系统响应器的可预测的包装。对于每一个处理函数,它在原生事件之外提供了一个新的gestureState对象:
onPanResponderMove: (event, gestureState) => {}
原生事件是指由以下字段组成的合成触摸事件:
nativeEvent
changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
identifier - 触摸点的 ID
locationX - 触摸点相对于父元素的横坐标
locationY - 触摸点相对于父元素的纵坐标
pageX - 触摸点相对于根元素的横坐标
pageY - 触摸点相对于根元素的纵坐标
target - 触摸点所在的元素 ID
timestamp - 触摸事件的时间戳,可用于移动速度的计算
touches - 当前屏幕上的所有触摸点的集合
一个gestureState对象有如下的字段:
stateID - 触摸状态的 ID。在屏幕上有至少一个触摸点的情况下,这个 ID 会一直有效。
moveX - 最近一次移动时的屏幕横坐标
moveY - 最近一次移动时的屏幕纵坐标
x0 - 当响应器产生时的屏幕坐标
y0 - 当响应器产生时的屏幕坐标
dx - 从触摸操作开始时的累计横向路程
dy - 从触摸操作开始时的累计纵向路程
vx - 当前的横向移动速度
vy - 当前的纵向移动速度
numberActiveTouches - 当前在屏幕上的有效触摸点的数量
基本用法
componentWillMount: function() {
this._panResponder = PanResponder.create({
// 要求成为响应者:
//开启点击手势响应
onStartShouldSetPanResponder: (evt, gestureState) => true,
//开启点击手势响应是否劫持 true:不传递给子view false:传递给子view
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
//开启移动手势响应
onMoveShouldSetPanResponder: (evt, gestureState) => true,
//开启移动手势响应是否劫持 true:不传递给子view false:传递给子view
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
//手指触碰屏幕那一刻触发 成为激活状态。
onPanResponderGrant: (evt, gestureState) => {
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
// gestureState.{x,y} 现在会被设置为0
},
//表示手指按下时,成功申请为事件响应者的回调。
onPanResponderStart: (evt, gestureState) => {
// 表示申请成功,你成为了事件的响应者,这个时候开始,组件就进入了激活状态。
},
onPanResponderMove: (evt, gestureState) => {
//最近一次的移动距离为gestureState.move{X,Y}
//从成为响应者开始时的累计手势移动距离为gestureState.d{x,y}
},
//当有其他不同手势出现,响应是否中止当前的手势
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
},
onPanResponderTerminate: (evt, gestureState) => {
// 另一个组件已经成为了新的响应者,所以当前手势将被取消。
},
onShouldBlockNativeResponder: (evt, gestureState) => {
// 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
// 默认返回true。目前暂时只支持android。
return true;
},
});
},
render: function() {
return (
<View {...this._panResponder.panHandlers} />
);
},
二、实现方式
① 官网Demo
链接 ===> https://reactnative.cn/docs/next/panresponder
结合Animated和PanResponder实现,效果如下:
② PanResponder实现Demo
代码如下
// dx 和 dy:从触摸操作开始到现在的累积横向/纵向路程
//
// moveX 和 moveY:最近一次移动时的屏幕横/纵坐标
//
// numberActiveTouches:当前在屏幕上的有效触摸点的数量
//
// stated:和之前一样,用来识别手指的ID
//
// vx 和 vy:当前横向/纵向移动的速度
//
// x0 和 y0:当触摸操作开始时组件相对于屏幕的横/纵坐标
import React, {PureComponent, Component} from 'react';
import {
StyleSheet,
View,
PanResponder,
} from 'react-native';
export default class TouchStartAndRelease extends PureComponent {
constructor(props) {
super(props);
this.state = {
backgroundColor: 'red',
marginTop: 100,
marginLeft: 100,
};
this.lastX = this.state.marginLeft;
this.lastY = this.state.marginTop;
}
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => {
return true;
},
onMoveShouldSetPanResponder: (evt, gestureState) => {
return true;
},
onPanResponderGrant: (evt, gestureState) => {
this._highlight();
},
onPanResponderMove: (evt, gestureState) => {
console.log(`gestureState.dx : ${gestureState.dx} gestureState.dy : ${gestureState.dy}`);
this.setState({
marginLeft: this.lastX + gestureState.dx,
marginTop: this.lastY + gestureState.dy,
});
},
onPanResponderRelease: (evt, gestureState) => {
this._unhighlight();
this.lastX = this.state.marginLeft;
this.lastY = this.state.marginTop;
},
onPanResponderTerminate: (evt, gestureState) => {
},
});
}
_unhighlight() {
this.setState({
backgroundColor: 'red',
});
}
_highlight() {
this.setState({
backgroundColor: 'blue',
});
}
render() {
return (
<View style={styles.container}>
<View style={[styles.redView,
{
backgroundColor: this.state.backgroundColor,
marginTop: this.state.marginTop,
marginLeft: this.state.marginLeft,
}
]}
{...this._panResponder.panHandlers}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
redView: {
width: 100,
height: 100,
}
});
三、拓展
手势移动与点击事件冲突解决办法
参考 react-native 手势移动与点击事件冲突解决办法
代码如下
this._panResponder = PanResponder.create({
onPanResponderGrant: (evt, gestureState) => {
//触摸开始
this.DragMove = false;
},
onPanResponderMove: (evt, gestureState) => {
/*
moveX - 最近一次移动时的屏幕横坐标
moveY - 最近一次移动时的屏幕纵坐标
x0 - 当响应器产生时的屏幕坐标
y0 - 当响应器产生时的屏幕坐标
dx - 从触摸操作开始时的累计横向路程
dy - 从触摸操作开始时的累计纵向路程
vx - 当前的横向移动速度
vy - 当前的纵向移动速度
*/
if (gestureState.dx !== 0 || gestureState.dy !== 0) {
this.DragMove = true;
}
},
onPanResponderRelease: (evt, gestureState) => {
//触摸释放
if(this.DragMove){//拖动
this.lastX = this.state.DragMarginLeft;
this.lastY = this.state.DragMarginTop;
}else{//点击
console.log("onPanResponderRelease OnClick")
}
},
});
四、参考
- rn实现一个view的拖拽
- react-native 手势移动与点击事件冲突解决办法
- RN-可拖动的悬浮按钮 动画+手势Android点击的时候会坐标跳变,我采用第一种方法