componentWillMount() {
this.panResponder = PanResponder.create({
/***************** 要求成为响应者 *****************/
// 单机手势是否可以成为响应者
onStartShouldSetPanResponder: (evt, gestureState) => true,
// 移动手势是否可以成为响应者
onMoveShouldSetPanResponder: (evt, gestureState) => true,
// 拦截子组件的单击手势传递,是否拦截
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
// 拦截子组件的移动手势传递,是否拦截
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
/***************** 响应者事件回调处理 *****************/
// 单击手势监听回调
onPanResponderGrant: (e, gestureState) => {
console.log('onPanResponderGrant==>' + '单击手势申请成功,开始处理手势')
this._onPanResponderGrant(e)
},
// 移动手势监听回调
onPanResponderMove: (e, gestureState) => {
console.log('onPanResponderMove==>' + '移动手势申请成功,开始处理手势' + `${gestureState}`)
this._onPanResponderMove(e, gestureState);
},
// 手势动作结束回调
onPanResponderEnd: (evt, gestureState) => {
console.log('onPanResponderEnd==>' + '手势操作完成了,用户离开')
this._onPanResponderEnd(evt)
},
// 手势释放, 响应者释放回调
onPanResponderRelease: (e) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
console.log('onPanResponderRelease==>' + '放开了触摸,手势结束')
},
// 手势申请失败,未成为响应者的回调
onResponderReject: (e) => {
// 申请失败,其他组件未释放响应者
console.log('onResponderReject==>' + '响应者申请失败')
},
// 当前手势被强制取消的回调
onPanResponderTerminate: (e) => {
// 另一个组件已经成为了新的响应者,所以当前手势将被取消
console.log('onPanResponderTerminate==>' + '由于某些原因(系统等),所以当前手势将被取消')
},
onShouldBlockNativeResponder: (evt, gestureState) => {
// 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
// 默认返回true。目前暂时只支持android。
return true;
},
})
}
一般使用手势的话,以上Api基本可以实现
render() {
return (
<View style={{flex: 1}}>
{this.renderNomalNavigationBar('触摸响应页')}
<TouchableOpacity
onPress={() => this.enterPenresnponderShow()}
style={{backgroundColor: '#00ffee', width: pxdp.width, height: pxdp.fixHeight(50)}}
/>
<View style={{
backgroundColor: this.state.isHeightShow ? '#ffddff' : '#ff0fa0',
borderColor: '#ff0',
borderWidth: 1,
borderRadius: pxdp.fixWidth(25),
width: pxdp.fixWidth(50),
height: pxdp.fixWidth(50),
position: 'absolute',
left: this.state.marginLeft,
top: this.state.marginTop,
}} {...this.panResponder.panHandlers}
/>
</View>
)
}
/**************************************** 逻辑处理 ****************************************/
/*
* 备注: 拖动小圆点的实现方法 - 使用绝对定位
* 对小圆点设置绝对布局,
* 通过触发开始的pageXY与moveXY的差值
* 来变更top,left的大小,
* position一定要为 absolute
* */
// 单点手势开始
_onPanResponderGrant(e) {
//1. 开始触发时,获取触摸点绝对位置
this.touchX = e.nativeEvent.locationX;
this.touchY = e.nativeEvent.locationY;
}
// 移动手势处理
_onPanResponderMove(evt, g) {
console.log(" ");
console.log(evt.nativeEvent);
console.log('=================')
console.log(g);
//2. 根据触发点计算真实的左侧,顶侧位移变化
let realMarginLeft = g.moveX - this.touchX;
let realMarginTop = g.moveY - this.touchY;
this.setState({
marginLeft: realMarginLeft,
marginTop: realMarginTop
})
}
// 手势结束
_onPanResponderEnd(evt) {
let pageX = evt.nativeEvent.pageX;
this.setState({
isHeightShow: false,
});
if (pageX <= pxdp.width / 4) {
pageX = 0;
this.setState({
marginLeft: pageX,
})
}
if (pageX >= pxdp.width * 3 / 4) {
pageX = pxdp.width - pxdp.fixWidth(50);
this.setState({
marginLeft: pageX,
})
}
}
以上是通过绝对布局的方式来实现了一个拖拽的小球,但不是所有地方都应该使用绝对布局来实现拖拽,有些地方不应该使用绝对布局,并且也难以实现,失去了Flex弹性盒子模型的优势
/**************************************** 渲染 ****************************************/
render() {
return (
<View style={{
width: pxdp.width, height: this.cellHeight, borderWidth: 1, borderColor: '#090',
borderRadius: 10, top: this.state.topOY, backgroundColor: '#00ddea'
}} {...this.panResponder.panHandlers}>
<Text>{this.props.title}</Text>
</View>
)
}
/**************************************** 逻辑 ****************************************/
//拖拽触发
_onPanResponderGrant(e, g) {
console.log('触发了: 点击Grant')
// console.log(e.nativeEvent.locationY);
// console.log(e.nativeEvent.pageY);
//1. 获取触摸点的绝对位置
this.touchY = e.nativeEvent.pageY;
}
//拖拽移动
_onPanResponderMove(e, g) {
console.log('触发了: 移动Move');
// console.log(e.nativeEvent);
// console.log(g);
//2. 计算top真正的移动距离
//2.1 获取移动过程中,在Y轴上移动了多少(该值不能直接作为top变量,否则不能实现拖拽精准移动)
let changeY = g.moveY - this.touchY;
//2.2 根据本次的Y轴移动及上次Y轴移动,计算本次真正的Y轴移动变化量
let realChangeY = changeY - this.lastChangeY;
//2.3 计算移动中, 触摸点真正的移动了多少距离
this.realChangeY = this.realChangeY + realChangeY;
//2.4 同理,top相对触摸点,也移动了同样的距离
this.setState({
topOY: this.realChangeY
});
//2.5 记录上次移动的距离
this.lastChangeY = changeY;
}
//结束拖拽
_onPanResponderRelease(e, g) {
console.log('释放了: 离开屏幕Release')
//3. 结束触发后, 清除本次交互时,记录的Y轴移动大小,避免影响下次再次交互
this.lastChangeY = 0;
}
PanResponder的交互过程中,会返回我们nativeEvent及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 - 当前在屏幕上的有效触摸点的数量
常用的有pageXY, 是相对于屏幕的触摸点
locationXY是相对于组件触摸点位置
moveXY则是每次移动过程中反馈出的上一次移动距离值
由于FLex弹性盒子模型的使用, 我们不太能用坐标点来改变组件的位置,因此,我们可以改变组件的top/left等属性
但是,由于直接使用moveXY等值去修改top/left等属性,会造成在刚触摸移动的时候,top/left等属性得到了触摸点的左边,会造成一定程度位置混乱偏移,因此,需要手动来计算top/left的变化情况, 而绝对布局下,这个问题则相对来说比较容易处理