react-native使用PanResponder实现pinch手势
一、RN中的高级手势功能PanResponder
PanResponder类可以将多点触摸操作协调成一个手势。它使得一个单点触摸可以接受更多的触摸操作,也可以用于识别简单的多点触摸手势。
它提供了一个对触摸响应系统响应器的可预测的包装。对于每一个处理函数,它在原生事件之外提供了一个新的gestureState对象。
onPanResponderMove: (event, gestureState) => {}
其中event对象有:
- 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,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
// gestureState.{x,y}0 现在会被设置为0
},
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} />
);
},
二、编写pinch处理
思路:通过numberActiveTouches获取手势的有效点,只处理2个手指的动作,通过touches点的集合,计算初始位置的直线距离和实时移动的直线距离,计算是放大还是缩放,使用距离作比例尺。
'use strict'
import React, { Component } from 'react'
import { View, StyleSheet, Text, PanResponder } from 'react-native'
import { Style, Const } from 'common'
export default class KLineView extends Component {
constructor(props) {
super(props);
this.initDistend,
this.state = {
point1x: 0,
point1y: 0,
point2x: 0,
point2y: 0,
scaleNumber: 0,
}
}
componentWillMount() {
this._pinchResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
onPanResponderStart: this._handlePanResponderStart,
})
}
componentDidMount() {
}
_handleStartShouldSetPanResponder = (event, gestureState) => {
if (gestureState.numberActiveTouches == 2) //两点手势则处理
{
return true;
}
return false;
}
_handleStartShouldSetPanResponderCapture = (event, gestureState) => {
if (gestureState.numberActiveTouches == 2) //两点手势则处理
{
return true;
}
return false;
}
_handleMoveShouldSetPanResponder = (event, gestureState) => {
if (gestureState.numberActiveTouches == 2) //两点手势则处理
{
return true;
}
return false;
}
_handleMoveShouldSetPanResponderCapture = (event, gestureState) => {
if (gestureState.numberActiveTouches == 2) //两点手势则处理
{
return true;
}
return false;
}
_handlePanResponderGrant = () => {
}
_handlePanResponderEnd = () => {
}
_handlePanResponderStart = (event, gestureState) => {
if (gestureState.numberActiveTouches == 2) //两点手势
{
//这里计算初始的距离 √(x1-x2)^2+(y1-y2)^2
let distend = Math.sqrt(
Math.pow(event.nativeEvent.touches[0].pageX - event.nativeEvent.touches[1].pageX, 2) +
Math.pow(event.nativeEvent.touches[0].pageY - event.nativeEvent.touches[1].pageY, 2)
)
this.initDistend = distend;
this.setState({
point1x: event.nativeEvent.touches[0].pageX,
point1y: event.nativeEvent.touches[0].pageY,
point2x: event.nativeEvent.touches[1].pageX,
point2y: event.nativeEvent.touches[1].pageY,
})
}
}
_handlePanResponderMove = (event, gestureState) => {
if (gestureState.numberActiveTouches == 2) //两点手势
{
let distend = Math.sqrt(
Math.pow(event.nativeEvent.touches[0].pageX - event.nativeEvent.touches[1].pageX, 2) +
Math.pow(event.nativeEvent.touches[0].pageY - event.nativeEvent.touches[1].pageY, 2)
)
let scaleNumber = distend - this.initDistend
this.setState({
point1x: event.nativeEvent.touches[0].pageX,
point1y: event.nativeEvent.touches[0].pageY,
point2x: event.nativeEvent.touches[1].pageX,
point2y: event.nativeEvent.touches[1].pageY,
scaleNumber: scaleNumber,
})
}
}
render() {
return (<View style={[Style.container, { backgroundColor: 'green', }]} {...this._pinchResponder.panHandlers}>
<Text>两点手势坐标</Text>
<View style={{flexDirection: 'row'}}>
<Text>缩放数值</Text>
<Text>{this.state.scaleNumber}</Text>
</View>
<View style={{ flex: 1, flexDirection: 'row' }}>
<View style={{ flex: 1 }}>
<Text>point1</Text>
<Text>x</Text>
<Text>{this.state.point1x}</Text>
<Text>y</Text>
<Text>{this.state.point1y}</Text>
</View>
<View style={{ flex: 1 }}>
<Text>point2</Text>
<Text>x</Text>
<Text>{this.state.point2x}</Text>
<Text>y</Text>
<Text>{this.state.point2y}</Text>
</View>
</View>
</View>)
}
}
// 默认基础样式
var baseStyle = StyleSheet.create({
container: {
flex: 1,
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: '#cbd2d9',
backgroundColor: '#FFF',
},
})