React Native框架底层的手势响应系统提供了响应处理器,PanResponder API将这些手势响应处理器再次进行封装,便于开发者对手势进行处理。
PanResponser API的基本思想就是:监视屏幕上指定的位置的矩形区域。对手指触发的事件作出响应。
一、利用PanResponser API监视的步骤
1、指定监视区域
为了监视一个区域,我们需要准备一个view或者是从view组件扩展而来的组件。(注意:如果要监视两个区域,一定不能让他们重叠,不然监视器无法工作)
2、定义监视器的相关变量
指向监视器的变量(必须)。
用来指向监视器监视区域的变量,可以不定义。
用来记录监视区域左上角顶点坐标的两个数值变量。可以不定义。但当触摸发生需要给用户视觉上的反馈时,有这个变量可以很容易实现反馈。
上一次触摸点的横、纵坐标变量。可以不定义,但这两个变量可以便于分析、处理触摸事件。
3、准备监视器的事件处理函数
一共有13个这样的函数,比如说onMoveShouldSetPanResponder用来判断是否要监视这个区域,onMoveShouldSetPanResponderCapture等。那我们只需要挑选出自己需要的函数来开发就可以了。
以下是监视器的13个事件处理函数
onMoveShouldSetPanResponder
onMoveShouldSetPanResponderCapture
onStartShouldSetPanResponder
onStartShouldSetPanResponderCapture
onPanResponderReject
onPanResponderGrant
onPanResponderStart
onPanResponderEnd
onPanResponderRelease
onPanResponderMove
onPanResponderTerminate
onPanResponderTerminateRequest
onShouldBlockNativeResponder
4、建立监视器
用PanResponder API提供的静态函数create,建立监听器
this.watcher = PanResponder.create({
……
})
5、将监视器和监视区域挂接
我们先假设一下,监视器就叫watcher。
{...this.watcher.panHandlers}
二、监视事件的生命周期
一般来说,在点击的生命周期我们自定义的被回调的函数都会收到两个参数,一个是原生事件,另一个是手势状态。
而这里面会有很多的成员变量比如说触摸点的位置,比如说手势状态的ID.
手势状态有以下变量
stateID—触摸状态的ID,在屏幕上至少有一个点的情况下,这个id会一直存在。
moveX—最近一次移动时的屏幕横坐标
moveY—最近一次移动时的屏幕纵坐标
x0—当响应器产生时的屏幕坐标
y0—当响应器产生时的屏幕坐标
dx—从触摸开始累积的横向路程
dy—从触摸操作开始累积纵向路程
vx—当前的横向移动速度
vy—当前的纵向移动速度
numberActiveTouches—当前在屏幕上的有效触摸点的数量。
三、单次点击事件的生命周期
onStartShouldSetPanResponderCapture:是否设置开始捕捉这次事件
onStartResponderStart:将这个事件视为点击事件的开始点
onPanResponderEnd:将这个事件视为点击事件的结束点。
这里列举出的三个生命周期方法是最常见的,但是其实它还有其他很多的方法。不过我们平常用的单次点击事件就是这三个。
在移动手势中,也有它自己的生命周期方法。这里不做详解。通过下面一个小的案例进行解说。
四、案例
滑动解锁:手指按压的滑块跟随手指移动,按压的监视区域随着手指移动而变化
从图中我们可以看到,在这个RN界面中需要返回一个顶级元素view,然后在里面添加一个滑块槽,之后是按钮。
这个按钮会有一个样式,我们可以将它切成一个圆的样子。并且,这个按钮是需要滑动的,所以要给它添加一个表示距离滑动槽原点的位置。而这个样式是需要及时改变的,所以我们可以定义一个状态机。用leftPoint来表示它的位置。
export default class GusDemo extends Component {
constructor(props){
super(props);
this.watcher = null; //监视器
this.startX = 0; //开始的左边
this.state = {leftPoint:1} //状态机变量用来保存最左边的卡槽
}
返回的UI界面
render() {
return (
<View style={styles.container}>
<View style = {styles.barViewStyle}>
<View style = {[styles.buttonViewStyle,{left:this.state.leftPoint}]}
{...this.watcher.panHandlers} //将监视器与监视区域挂接
/>
</View>
</View>
);
}
设置样式
首先要获取宽度。
var Dimensions = require('Dimensions');
var totalWidth = Dimensions.get('window').width; //宽度
设置样式
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
barViewStyle: {
width:totalWidth - 40,
height:50,
backgroundColor:'grey',
borderRadius:25,
left:20,
top:50,
flexDirection:'row',
},
buttonViewStyle: {
width:48,
height:48,
borderRadius:24,
backgroundColor:'pink',
left:1,
top:1,
}
});
自此,所有的UI部分已经构建完毕,现在要做的就是到componentWillMount()方法里面去建立监视器。为啥要在这个方法里面呢,是因为这个方法在UI渲染之前运行的,我们可以让它来做一些定义变量或赋值的操作。所以我们将事件的按下、移动和结束的方法都写到这边来。分别给这几个属性各自定义一个方法。
componentWillMount(){
this.watcher = PanResponder.create({ //建立监视器
onStartShouldSetPanResponder:()=>true, //判断是否要监听,这里直接返回true
onPanResponderGrant:this._onPanResponderGrant, //事件,按下
onPanResponderMove:this._onPanResponderMove, //移动
onPanResponderEnd:this._onPanResponderEnd, //结束
});
}
这些方法我们都用简写的方式,所以到构造函数中将它们绑定一下。
export default class GusDemo extends Component {
constructor(props){
super(props);
this.watcher = null; //监视器
this.startX = 0; //开始的左边
this.state = {leftPoint:1} //状态机变量用来保存最左边的卡槽
this._onPanResponderGrant = this._onPanResponderGrant.bind(this);
this._onPanResponderEnd = this._onPanResponderEnd.bind(this);
this._onPanResponderMove = this._onPanResponderMove.bind(this);
}
现在来具体实现自定义的方法。虽然我们看到的是简写的方法,但是实际上,系统按下的方法会给我们自定义的这个方法传入两个参数,一个是事件,而另外一个是手指触摸的位置。在开始的时候,我们要将开始偏移的位置给记录下来。因为每次开始滑动的时候位置其实都是不一样的。
_onPanResponderGrant(e,gestureState){
this.startX = gestureState.x0; //按住滑块的时候,记录偏移量
}
下面来写移动按钮的时候的逻辑
_onPanResponderMove(e,gestureState){
let leftPoint; //用一个变量记录滑动的偏移值
if(gestureState.moveX > totalWidth-42-48+this.startX){ //正常位置
leftPoint = totalWidth - 42 - 48;
}else{
leftPoint = gestureState.moveX - this.startX; //在后面可以写解锁或者是跳转效果。
}
this.setState(()=>{
return {leftPoint}; //改变状态机
})
}
当手指松开之后,我们在这里不做复杂的判断,直接让它移动到最原始的位置。
_onPanResponderEnd(e,gestureState){
let leftPoint = 1;
this.setState(()=>{
return {leftPoint}; //改变状态机到1的位置
})
}
滑动解锁的案例就完成了。
下面是源码index.ios.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
PanResponder
} from 'react-native';
var Dimensions = require('Dimensions');
var totalWidth = Dimensions.get('window').width; //宽度
export default class GusDemo extends Component {
constructor(props){
super(props);
this.watcher = null; //监视器
this.startX = 0; //开始的左边
this.state = {leftPoint:1} //状态机变量用来保存最左边的卡槽
this._onPanResponderGrant = this._onPanResponderGrant.bind(this);
this._onPanResponderEnd = this._onPanResponderEnd.bind(this);
this._onPanResponderMove = this._onPanResponderMove.bind(this);
}
componentWillMount(){
this.watcher = PanResponder.create({ //建立监视器
onStartShouldSetPanResponder:()=>true,
onPanResponderGrant:this._onPanResponderGrant, //事件,按下
onPanResponderMove:this._onPanResponderMove, //移动
onPanResponderEnd:this._onPanResponderEnd, //结束
});
}
//移动的逻辑
_onPanResponderGrant(e,gestureState){
this.startX = gestureState.x0; //按住滑块的时候,记录偏移量
}
_onPanResponderMove(e,gestureState){
let leftPoint; //用一个变量记录滑动的偏移值
if(gestureState.moveX > totalWidth-42-48+this.startX){ //正常位置
leftPoint = totalWidth - 42 - 48;
}else{
leftPoint = gestureState.moveX - this.startX; //在后面可以写解锁或者是跳转效果。
}
this.setState(()=>{
return {leftPoint}; //改变状态机
})
}
_onPanResponderEnd(e,gestureState){
let leftPoint = 1;
this.setState(()=>{
return {leftPoint}; //改变状态机到1的位置
})
}
render() {
return (
<View style={styles.container}>
<View style = {styles.barViewStyle}>
<View style = {[styles.buttonViewStyle,{left:this.state.leftPoint}]}
{...this.watcher.panHandlers}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
barViewStyle: {
width:totalWidth - 40,
height:50,
backgroundColor:'grey',
borderRadius:25,
left:20,
top:50,
flexDirection:'row',
},
buttonViewStyle: {
width:48,
height:48,
borderRadius:24,
backgroundColor:'pink',
left:1,
top:1,
}
});
AppRegistry.registerComponent('GusDemo', () => GusDemo);