ReactNative触摸事件

1、基本的触摸组件

  • Text
  • Button
  • Touchable系列组件
    • TouchableHighlight
    • TouchableNativeFeedback
    • TouchableOpacity
    • TouchableWithoutFeedback

这些组件都支持onPressInonPressOutonPressonLongPress方法。

使用TouchableOpacity示例:

<TouchableOpacity  
  onPressIn={() => console.log("onPressIn")}
  onPressOut={() => console.log("onPressOut")}
  onPress={() => console.log("onPress")}
  onLongPress={() => console.log("onLongPress")}
  >
  <Image
    style={styles.btn_login}
    source={require('./img/btn_login.png')} />
</TouchableHighlight>  
复制代码

Touch*手势的区别:

1、TouchableHighlight,除了给内部元素增加绑定事件之外,还负责给内部元素增加点击的效果;
2、TouchableNativeFeedback只能用在安卓平台上,它可以针对点击在点击区域中显示不同的效果,例如安卓系统中的点击波纹效果;
3、TouchableOpacity这个组件用来给为内部元素在点击时添加透明度;
4、TouchableWithoutFeedback这个组件只响应touch手势,不增加点击态,不推荐使用。

2、手势响应系统

2.1单组件触摸事件处理

RN的组件默认不进行处理触摸事件,但是我们可以将一个普通的View组件变成一个能响应手势操作的responder,只需要在props上设置如下方法:

View.props.onStartShouldSetResponder
View.props.onMoveShouldSetResponder
View.props.onResponderGrant
View.props.onResponderReject
View.props.onResponderStart
View.props.onResponderMove
View.props.onResponderEnd
View.props.onResponderRelease
View.props.onResponderTerminationRequest
View.props.onResponderTerminate
复制代码

React Native事件响应的基本步骤如下:

1、如果组件要进行触摸事件处理,首先要申请成为事件响应者,通过如下两个属性询问组件是否成为响应者:

  • View.props.onStartShouldSetResponder: (evt) => true,在触摸开始的时候(TouchDown),ReactNative会先调用此函数,询问组件是否需要成为事件的响应者;
  • View.props.onMoveShouldSetResponder: (evt) => true,如果View不是响应者,那么在每一个触摸点开始移动;

2、因为同一时刻,只能有一个事件处理响应者,所以并不是每个组件都能申请成功,RN通过如下两个回调告知申请结果:

  • View.props.onResponderGrant: (evt) => {}:表示申请成功,组件成为了事件处理响应者,这时组件就开始接收后序的触摸事件输入。一般情况下,这时开始,组件进入了激活状态,并进行一些事件处理或者手势识别的初始化。
  • View.props.onResponderReject: (evt) => {}:表示申请失败了,这意味者其他组件正在进行事件处理,并且它不想放弃事件处理,所以你的申请被拒绝了,后续输入事件不会传递给本组件进行处理。

3、如果组件申请成为了响应者,会执行如下回调方法:

  • View.props.onResponderStart: (evt) => {}:表示手指按下时,成功申请为事件响应者的回调;
  • View.props.onResponderMove: (evt) => {}:表示触摸手指移动的事件,这个回调可能非常频繁,所以这个回调函数的内容需要尽量简单;
  • View.props.onResponderEnd: (evt) => {}:表示组件结束事件响应。
  • View.props.onResponderRelease: (evt) => {}:表示触摸完成(touchUp)的时候的回调,表示用户完成了本次的触摸交互,这里应该完成手势识别的处理,这以后,组件不再是事件响应者,组件取消激活。

4、当组件处于响应者期间,并且其他组件也在申请触摸事件处理时,RN会询问是否可以释放响应让给其他组件:

  • View.props.onResponderTerminationRequest: (evt) => bool

5、最终释放响应者的角色后,回调onResponderTerminate,通知组件事件响应处理被终止:

可能是由于其他 View 通过onResponderTerminationRequest请求的,也可能是由操作系统强制夺权(比如 iOS 上的控制中心或是通知中心)

  • View.props.onResponderTerminate: (evt) => {}

示例代码:

import React, {Component} from 'react';
import {
	StyleSheet,
	View,
	Button,
	Text
} from 'react-native';


export default class GestureScreen extends Component {

	constructor(props) {
		super(props);
		this.state = {
			backgroundColor: 'blue'
		};
		this.gestureHandlers = {
			onStartShouldSetResponder: () => true,
			onMoveShouldSetResponder: () => true,
			onResponderStart:() => {
				console.log('onResponderStart')
			},
			onResponderGrant: () => {
				console.log('onResponderGrant');
				this.setState({backgroundColor: 'green'})
			},
			onResponderMove: () => {
				console.log('onResponderMove')
			},
			onResponderEnd:() => {
				console.log('onResponderEnd')
			},
			onResponderRelease: () => {
				console.log('onResponderRelease')
				this.setState({backgroundColor: 'blue'})
			},
			onResponderTerminationRequest: () => {
				console.log('onResponderTerminationRequest');
				return true;
			},
			onResponderTerminate: (evt) => {
				console.log('onResponderTerminate')
			}
		};
	}


	render() {
		return (
			<View style={styles.container}>
				<View
					{...this.gestureHandlers}
					style={[styles.gesview,{backgroundColor:this.state.backgroundColor}]}>
					<Text style={styles.text}>View 1</Text>
				</View>

			</View>
		);
	}
}

const styles = StyleSheet.create({
	container: {
		flex: 1,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor:'rgb(236,236,236)'
	},
	gesview:{
		width:160,
		height:80,
		borderRadius:20,
		borderColor:'red',
		borderWidth:2,
		alignItems:'center',
		justifyContent:'center',
	},
	text:{
		color:'white',
		fontSize:20,
		fontWeight:'bold'
	}

});
复制代码

执行这段代码,responderGrant响应,中间绿色的View变为蓝色,responderRelease响应,重新变为绿色; 点击一次View,然后离开屏幕,调用顺序:

  • 1、onResponderGrant
  • 2、onResponderStart
  • 3、onResponderEnd
  • 4、onResponderRelease

2.2嵌套组件触摸事件处理

如果父View A中嵌套有一个子组件View B,

正常的情况,当用户触摸View B时,子组件会显示:B 子组件 被点击,父View则不会有反应;当点击View A时,View A会显示:A 组件 被点击,子View B没有反应,以保证视图都能够正常处理触摸事件。

示例

import React, {Component} from 'react';
import {
	StyleSheet,
	View,
	Button,
	Text
} from 'react-native';


export default class GestureScreen extends Component {

	constructor(props) {
		super(props);
		this.state = {
			text1: 'A 父组件',
			text2:'B 子组件'
		};
		this.gestureHandlers = {
			onStartShouldSetResponder: () => true,
			onMoveShouldSetResponder: () => true,
			onResponderStart: () => {
				console.log('onResponderStart')
			},
			onResponderGrant: () => {
				console.log('onResponderGrant');
				this.setState({text1: 'A 父组件 被点击'})
			},
			onResponderMove: () => {
				console.log('onResponderMove')
			},
			onResponderEnd: () => {
				console.log('onResponderEnd')
			},
			onResponderRelease: () => {
				console.log('onResponderRelease')
				this.setState({text1:'A 父组件'})
			},
		};
		this.gestureHandlers2 = {
			onStartShouldSetResponder: () => true,
			onMoveShouldSetResponder: () => true,
			onResponderStart: () => {
				console.log('onResponderStart---->2')
			},
			onResponderGrant: () => {
				console.log('onResponderGrant---->2');
				this.setState({text2: 'B 子组件 被点击'})
			},
			onResponderMove: () => {
				console.log('onResponderMove---->2')
			},
			onResponderEnd: () => {
				console.log('onResponderEnd---->2')
			},
			onResponderRelease: () => {
				console.log('onResponderRelease---->2')
				this.setState({text2: 'B 子组件'})
			},
		};
	}


	render() {
		return (
			<View style={styles.container}>
				<View
					{...this.gestureHandlers}
					style={styles.gesview}>
					<Text style={styles.text}>{this.state.text1}</Text>

					<View
						style={styles.sonview}
						{...this.gestureHandlers2}>
						<Text style={styles.text}>{this.state.text2}</Text>
					</View>
				</View>

			</View>
		);
	}
}

const styles = StyleSheet.create({
	container: {
		flex: 1,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: 'rgb(236,236,236)'
	},
	gesview: {
		width: 200,
		height: 200,
		backgroundColor:'#B34D76',
	},
	text: {
		color: 'white',
		fontSize: 20,
		fontWeight: 'bold'
	},
	sonview: {
		width:100,
		height:100,
		backgroundColor:'#33AECC',
		alignSelf:'center',
		marginTop:20,
	}

});
复制代码

但是,在 RN 中,默认情况下使用冒泡机制,响应最深的组件最先开始响应。所以当多个 View 同时在*ShouldSetResponder中返回 true 时,最底层的 View 将最先响应。 RN 提供了一个劫持机制,在触摸事件往下传递的时候,先询问父组件是否需要劫持,不给子组件传递事件,也就是如下两个回调:

  • View.props.onStartShouldSetResponderCapture: (evt) => true
  • View.props.onMoveShouldSetResponderCapture: (evt) => true

我们设置View A的以上两个属性为true时,效果:

可见点击B子View时,A父View显示被点击,点击A View,依然是A被点击;B的事件被父View拦截并处理,并且不再继续传递。

2.3事件数据结构

触摸事件处理的回调都有一个 evt 参数,包含一个触摸事件数据 nativeEvent。nativeEvent 的详细内容如下

  • changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
  • identifier - 触摸点的 ID
  • locationX - 触摸点相对于当前元素的横坐标
  • locationY - 触摸点相对于当前元素的纵坐标
  • pageX - 触摸点相对于根元素的横坐标
  • pageY - 触摸点相对于根元素的纵坐标
  • target - 触摸点所在的元素 ID
  • timestamp - 触摸事件的时间戳,可用于移动速度的计算
  • touches - 当前屏幕上的所有触摸点的集合

3、PanResponder

RN提供了内置的手势识别库PanResponder,它封装了上面的事件回调函数,对触摸事件数据进行加工,完成滑动手势识别: PanResponder的每个方法,除了第一个evt参数之外,还可以使用第二个参数gestureState,gestureState是一个对象,包含手势进行过程中更多的信息

3.1 一个gestureState对象有如下的字段:

  • stateID - 触摸状态的 ID。在屏幕上有至少一个触摸点的情况下,这个 ID 会一直有效。
  • moveX - 最近一次移动时的屏幕横坐标
  • moveY - 最近一次移动时的屏幕纵坐标
  • x0 - 当响应器产生时的屏幕坐标
  • y0 - 当响应器产生时的屏幕坐标
  • dx - 从触摸操作开始时的累计横向路程
  • dy - 从触摸操作开始时的累计纵向路程
  • vx - 当前的横向移动速度
  • vy - 当前的纵向移动速度
  • numberActiveTouches - 当前在屏幕上的有效触摸点的数量

3.2 PanResponder.create()方法中,可配置的属性:

onMoveShouldSetPanResponder: (e, gestureState) => {...}
onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}
onStartShouldSetPanResponder: (e, gestureState) => {...}
onStartShouldSetPanResponderCapture: (e, gestureState) => {...}
onPanResponderReject: (e, gestureState) => {...}
onPanResponderGrant: (e, gestureState) => {...}
onPanResponderStart: (e, gestureState) => {...}
onPanResponderEnd: (e, gestureState) => {...}
onPanResponderRelease: (e, gestureState) => {...}
onPanResponderMove: (e, gestureState) => {...}
onPanResponderTerminate: (e, gestureState) => {...}
onPanResponderTerminationRequest: (e, gestureState) => {...}
onShouldBlockNativeResponder: (e, gestureState) => {...}
复制代码

3.3 使用PanResponder,简单实现一个拖拽视图:


import React, {Component} from 'react';
import {
	StyleSheet,
	View,
	PanResponder
} from 'react-native';


export default class GestureScreen extends Component {

	constructor(props) {
		super(props);
		this.state = {
			top:0,
			left:0
		};

		this.panResponder = PanResponder.create({
			onStartShouldSetPanResponder: () => true,
			onMoveShouldSetPanResponder: ()=> true,
			onPanResponderGrant: ()=>{
				this.top = this.state.top;
				this.left = this.state.left;
			},
			onPanResponderMove: (evt,gestureState)=>{
				this.setState({
					top: this.top+gestureState.dy,
					left: this.left+gestureState.dx
				})
			},
			onPanResponderRelease: (evt,gestureState)=>{
				this.setState({
					top: this.top+gestureState.dy,
					left: this.left+gestureState.dx
				})}
		})
	}


	render() {
		return (
			<View style={styles.container}>
				<View
					{...this.panResponder.panHandlers}
					style={[styles.dragview,{
						top: this.state.top,
						left: this.state.left
					}]}/>
			</View>
		);
	}
}

const styles = StyleSheet.create({
	container: {
		flex: 1,
		justifyContent: 'center',
		alignItems: 'center',
		backgroundColor: '#F5FCFF',
	},
	dragview: {
		width: 100,
		height: 100,
		backgroundColor:'red',
		borderRadius:50,
	}

});
复制代码

4、总结

通过上面的介绍,可以看到 RN 中提供了类似 Native 平台的事件处理机制,所以也可以实现各种的触摸事件处理,甚至也可以实现复杂的手势识别。

另外需要注意,因为 RN 的异步通信和执行机制,前面描述的所有回调函数都是在 JS 线程中,并不是 Native 的 UI 线程,而 Native 平台的 Touch 事件都是在 UI 线程中。所以在 JS 中通过 Touch 或者手势实现动画,可能会延迟的问题。

本文是浅析,介绍比较简单。可能存在诸多错误和不足之处,欢迎交流。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值