最近想封装一个滑动组件给项目其他组件使用,React Native官方文档中就PanResponder的使用有做大致的讲解,同时网上也有各种关于PanResponder的详解教程,但大多只是官网的一个摘抄,并且就响应事件(onStartShouldSetPanResponder、onStartShouldSetPanResponderCapture、onMoveShouldSetPanResponder、onMoveShouldSetPanResponderCapture
)的参数如何配置并没有详细解答,导致开始接触的时候对于React Native
的事件传递和响应比较模糊。本次希望各封装一个Hoc和Hook组件,来实现手势滑动,且同时不影响被包裹的子组件事件响应,因此进行这次的PanResponder探究
重点结论
- 不能在
TouchableOpacity
中使用触摸 - 被设置了
PanResponder
的View
,如果内部有TouchableOpacity
,只有当onStartShouldSetPanResponderCapture
为false
时,内部的事件响应才能被执行
详细分析
决定view的事件响应主要由四个函数的返回值决定:
onStartShouldSetPanResponder
:确定是否在view组在手指按下的时候响应touch事件
onStartShouldSetPanResponderCapture
:确定是否在view组件被按下的时候响应touch事件后,是否阻止事件冒泡(它的子组件的事件将不被响应)
onStartShouldSetPanResponder
:确定是否在view组件手指移动的时候响应touch事件
onMoveShouldSetPanResponderCapture
:确定是否在view组件手指移动的时候响应touch事件后,是否阻止事件冒泡(它的子组件的事件将不被响应)
onStartShouldSetPanResponder: boolean,
onStartShouldSetPanResponderCapture: boolean,
onMoveShouldSetPanResponder: boolean,
onMoveShouldSetPanResponderCapture: boolean,
项目配置方式
- 将事件全部交给PanResponder来处理,子组件中不需要响应事件,则可以将以上返回全部配置成
ture
- 子组件中有自己的事件需要响应,则必须将onStartShouldSetPanResponderCapture配置成
false
,其他根据自己的需求进行配置
我们项目的需求大体是,只希望PanResponder
处理move
事件,子组件中有自己的onPress
事件响应,因此,我们的项目配置如下:
// 要求成为手势响应者,且不阻止点击事件冒泡(onStartShouldSetPanResponderCapture-false)
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
探究过程
我们在代码中将这4个返回值设置为state
状态值,通过修改成true
或false
,来进行不同的组合。同时,在View
组件内部添加TouchableOpacity
并设置onPress
来响应点击事件(如果对touch
事件感兴趣的同学可以里面在嵌套一个View
同时设置PanResponder
,我只是基于项目目前的场景来设定onPress
来先满足需要,原理其实通过此文是可以一并探究的)
文末会贴上我们的demo全量代码
重要代码:
constructor(props: any) {
super(props)
this.state = {
hint: '',
onStartShouldSetPanResponder: false,
onStartShouldSetPanResponderCapture: false,
onMoveShouldSetPanResponder: false,
onMoveShouldSetPanResponderCapture: false,
}
// this.createPanResponder()
this._panResponder = PanResponder.create({
// 要求成为响应者:
onStartShouldSetPanResponder: () => this.state.onStartShouldSetPanResponder,
onStartShouldSetPanResponderCapture: () => this.state.onStartShouldSetPanResponderCapture,
onMoveShouldSetPanResponder: () => this.state.onMoveShouldSetPanResponder,
onMoveShouldSetPanResponderCapture: () => this.state.onMoveShouldSetPanResponderCapture,
onPanResponderGrant: (evt, gestureState) => {
this.log('onPanResponderGrant --- 事件响应、开始')
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
},
onPanResponderMove: (evt, gestureState) => {
this.log('onPanResponderMove ---- 滑动')
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
this.log('onPanResponderRelease ---- 事件结束')
},
onPanResponderTerminate: (evt, gestureState) => {
// 另一个组件已经成为了新的响应者,所以当前手势将被取消。
this.log('onPanResponderTerminate ---- 被取消了')
},
})
}
//其他代码段
render() {
return (
<View style={
{
flex: 1,
padding: 10,}}>
<View>
<View
style={
PanResponderDemoStyle.contain}
{
...this._panResponder.panHandlers}
>
<TouchableOpacity
style={
PanResponderDemoStyle.touchView}
onPress={
()=>this.log('onPress --- 按钮事件')}
><Text>TouchableOpacity按钮</Text></TouchableOpacity>
<Text>事件响应日志:</Text>
<Text>
{
this.state.hint}
</Text>
</View>
</View>
)
}
组合效果
名称定义:
TouchableOpacity
组件:被设定了PanResponder
的View
包裹的可以点击的子组件View
组件:不包含TouchableOpacity
组件的View
中的其他区域touch
事件:包含onPanResponderGrant
和onPanResponderRelease
的回调响应move
事件:指onPanResponderMove
的回调响应
注意问题
不管onStartShouldSetPanResponder
等true
或false
如何设置,View
组件都会响应touch
和move
事件,以下主要关注TouchableOpacity
组件的事件响应情况(通过这个对比,可以解决通过Hoc将这个手势功能添加给任意一个字组件中遇到的事件响应问题)
onStartShouldSetPanResponder
为true
,其他为false
TouchableOpacity
组件内部点击会响应onPress事件,不响应touch和move事件,就算在TouchableOpacity
组件内移动手指,在弹起后,依旧会响应onPress事件
onStartShouldSetPanResponderCapture
为true
,其他为false
TouchableOpacity
组件内部点击不会响应onPress事件,只会响应touch和move事件
onMoveShouldSetPanResponder
为true
,其他为false
TouchableOpacity
组件如果只点击不滑动,在手指离开的时候会响应onPress事件并且不会响应touch
事件,TouchableOpacity
组件手指按下后并滑动,则会响应touch
和move
事件
onMoveShouldSetPanResponderCapture
为true
,其他为false
TouchableOpacity
只点击不滑动,在手指离开的时候会响应onPress
事件,但不响应touch
事件TouchableOpacity
组件手指按下后滑动,不响应onPress
事件,同时会响应touch
和move
事件TouchableOpacity
只点击不滑动,会触发onLongPress
事件,并且此时再滑动,依旧会触发touch
和move
事件View
组件点击不触发touch
事件
Demo全量代码
import React from 'react'
import {
View,
Text,
StyleSheet, PanResponderInstance, PanResponder, TouchableOpacity,
} from 'react-native'
export class PanResponderDemoView extends React.Component<any, {
hint: string,
onStartShouldSetPanResponder: boolean,
onStartShouldSetPanResponderCapture: boolean,
onMoveShouldSetPanResponder: boolean,
onMoveShouldSetPanResponderCapture: boolean,
}> {
_panResponder: PanResponderInstance
constructor(props: any) {
super(props)
this.state = {
hint: '',
onStartShouldSetPanResponder: false,
onStartShouldSetPanResponderCapture: false,
onMoveShouldSetPanResponder: false,
onMoveShouldSetPanResponderCapture: false,
}
// this.createPanResponder()
this._panResponder = PanResponder.create({
// 要求成为响应者:
onStartShouldSetPanResponder: () => this.state.onStartShouldSetPanResponder,
onStartShouldSetPanResponderCapture: () => this.state.onStartShouldSetPanResponderCapture,
onMoveShouldSetPanResponder: () => this.state.onMoveShouldSetPanResponder,
onMoveShouldSetPanResponderCapture: () => this.state.onMoveShouldSetPanResponderCapture,
onPanResponderGrant: (evt, gestureState) => {
this.log('onPanResponderGrant --- 事件响应、开始')
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
},
onPanResponderMove: (evt, gestureState) => {
this.log('onPanResponderMove ---- 滑动')
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
this.log('onPanResponderRelease ---- 事件结束')
},
onPanResponderTerminate: (evt, gestureState) => {
// 另一个组件已经成为了新的响应者,所以当前手势将被取消。
this.log('onPanResponderTerminate ---- 被取消了')
},
})
}
log(text: string) {
console.log(text, this.state.hint)
this.setState({
hint: this.state.hint + '\n' + text,
})
}
render() {
return (
<View style={
{
flex: 1,
padding: 10,}}>
<View>
<TouchableOpacity
style={
PanResponderDemoStyle.touchView}
onPress={
()=>this.log('onPress --- 按钮事件')}
onLongPress={
()=>this.log('onLongPress --- 按钮长按事件')}
><Text>TouchableOpacity按钮</Text></TouchableOpacity>
<Text onPress={
()=>this.setState({
hint:''})}>清空日志</Text>
<StateCell
name={
'onStartShouldSetPanResponder'} select={
this.state.onStartShouldSetPanResponder}
onPress={
() => this.setState({
onStartShouldSetPanResponder: !this.state.onStartShouldSetPanResponder})}/>
<StateCell
name={
'onStartShouldSetPanResponderCapture'} select={
this.state.onStartShouldSetPanResponderCapture}
onPress={
() => this.setState({
onStartShouldSetPanResponderCapture: !this.state.onStartShouldSetPanResponderCapture})}/>
<StateCell
name={
'onMoveShouldSetPanResponder'} select={
this.state.onMoveShouldSetPanResponder}
onPress={
() => this.setState({
onMoveShouldSetPanResponder: !this.state.onMoveShouldSetPanResponder})}/>
<StateCell
name={
'onMoveShouldSetPanResponderCapture'} select={
this.state.onMoveShouldSetPanResponderCapture}
onPress={
() => this.setState({
onMoveShouldSetPanResponderCapture: !this.state.onMoveShouldSetPanResponderCapture})}/>
</View>
<View
style={
PanResponderDemoStyle.contain}
{
...this._panResponder.panHandlers}
>
<TouchableOpacity
style={
PanResponderDemoStyle.touchView}
onPress={
()=>this.log('onPress --- 按钮事件')}
onLongPress={
()=>this.log('onLongPress --- 按钮长按事件')}
><Text>TouchableOpacity按钮</Text></TouchableOpacity>
<Text>事件响应日志:</Text>
<Text>
{
this.state.hint}
</Text>
</View>
</View>
)
}
}
class StateCell extends React.PureComponent<{
name: string, select: boolean, onPress: (() => void) }> {
get selectText() {
return this.props.select ? 'true' : 'false'
}
render() {
const {
name, select} = this.props
return (
<View style={
{
flexDirection: 'row',
justifyContent: 'space-between',
}}>
<Text>{
`${
name}:`}</Text>
<View style={
{
flexDirection: 'row',
justifyContent:'flex-end',
width: 120,
}}>
<Text style={
{
color: select ? 'red' : '#666',marginRight:4}}>{
this.selectText}</Text>
<Text style={
{
color: '#18ABFB', fontSize: 14}} onPress={
() => this.props.onPress()}>{
'修改'}</Text></View>
</View>
)
}
}
const PanResponderDemoStyle = StyleSheet.create({
contain: {
flex: 1,
paddingTop: 10,
alignItems: 'center',
backgroundColor: '#F2F2F2',
},
touchView: {
width: 300,
height: 100,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'green',
},
})