处理手势 - ReactNative

TODO;动画

PanResponder类可以将多点触摸操作协调成一个手势。它使得一个单点触摸可以接受更多的触摸操作,也可以用于识别简单的多点触摸手势。

默认情况下PanResponder会通过InteractionManager来阻止长时间运行的JS 事件打断当前的手势活动。

它提供了一个对触摸响应系统响应器的可预测的包装。对于每一个处理函数,它在原生事件之外提供了一个新的gestureState对象:

onPanResponderMove: (event, gestureState) => {}

event,具有如下的属性:

属性说明
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
      },
      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} />
    );
  },

代码示例

import React, { Component } from "react";
import { Animated, View, StyleSheet, PanResponder, Text } from "react-native";

class App extends Component {
    pan = new Animated.ValueXY();
    panResponder = PanResponder.create({
        onMoveShouldSetPanResponder: () => true,
        onPanResponderGrant: () => {
            console.log(this.pan.x, this.pan.y);

            this.pan.setOffset({
                x: this.pan.x._value,
                y: this.pan.y._value,
            });
        },
        onPanResponderMove: Animated.event([
            null,
            { dx: this.pan.x, dy: this.pan.y },
        ]),
        onPanResponderRelease: () => {
            this.pan.flattenOffset();
        },
    });

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.titleText}>Drag this box!</Text>
                <Animated.View
                    style={{
                        transform: [
                            { translateX: this.pan.x },
                            { translateY: this.pan.y },
                        ],
                    }}
                    {...this.panResponder.panHandlers}
                >
                    <View style={styles.box} />
                </Animated.View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: "center",
        justifyContent: "center",
    },
    titleText: {
        fontSize: 14,
        lineHeight: 24,
        fontWeight: "bold",
    },
    box: {
        height: 150,
        width: 150,
        backgroundColor: "blue",
        borderRadius: 5,
    },
});

export default App;

上面的代码执行完毕之后,会得到诸如下面的执行动态图

VeryCapture_20230529033850.gif