React-native中的手势系统

​ 移动设备上的手势识别要比在web上复杂得多。用户的一次触摸操作的真实意图是什么,App要经过好几个阶段才能判断。比如App需要判断用户的触摸到底是在滚动页面,还是滑动一个widget,或者只是一个单纯的点击。甚至随着持续时间的不同,这些操作还会转化。此外,还有多点同时触控的情况。

​ 触摸响应系统可以使组件在不关心父组件或子组件的前提下自行处理触摸交互。

手势组件

react-native中预置的处理手势的组件

​ react-native中是没有click等事件的,需要借助封装的好的组件配合(onpress 或`onLongPress)完成手势处理

组件:

  • TouchableHighlight 触摸屏幕会有高亮显示

  • TouchableNativeFeedback 触摸屏幕会有水波纹效果(仅限Android)

  • TouchableOpacity 触摸屏幕改变当前元素的透明度

  • TouchableWithoutFeedback 触摸屏幕无任何效果

事件:

  • onPress 点击组件时触发

  • onLongPress 长按组件时触发

手势系统

​ 单单依靠单击和长按是无法满足移动App多种手势的需求的,此时需要利用收拾系统。

  • 引入

    1
    import { PanResponder } from "react-native";
  • 配置手势

    • 处理函数:
      • onStartShouldSetPanResponder 用户开始触摸屏幕的时候,是否愿意成为响应者;默认返回false,无法响应,当返回true的时候则可以进行之后的事件传递。默认为false
      • onMoveShouldSetPanResponder 在每一个触摸点开始移动的时候,再询问一次是否响应触摸交互;认为false
      • onPanResponderGrant 开始手势操作,也可以说按下去。给用户一些视觉反馈,让他们知道发生了什么事情!(如:可以修改颜色)
      • onPanResponderMove 最近一次的移动距离.如:(获取x轴y轴方向的移动距离 gestureState.dx,gestureState.dy)
      • onPanResponderRelease 用户放开了所有的触摸点,且此时视图已经成为了响应者。
      • onPanResponderTerminate 另一个组件已经成为了新的响应者,所以当前手势将被取消。
    • 参数
      • evt
        • nativeEvent
          • changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
          • identifier - 触摸点的ID
          • locationX - 触摸点相对于父元素的横坐标
          • locationY - 触摸点相对于父元素的纵坐标
          • pageX - 触摸点相对于根元素的横坐标
          • pageY - 触摸点相对于根元素的纵坐标
          • target - 触摸点所在的元素ID
          • timestamp - 触摸事件的时间戳,可用于移动速度的计算
          • touches - 当前屏幕上的所有触摸点的集合
        • gestureState
          • dx/dy - 手势进行到现在的横向/纵向相对位移
          • vx/vy - 此刻的横向/纵向速度
          • numberActiveTouches - responder上的触摸的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一般来说 手势的创建放在  componentWillMount  生命周期中
componentWillMount(evt, gestureState) {
// 手势创建
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
//onStartShouldSetPanResponder为true时立即执行
},
onPanResponderMove: (evt, gestureState) => {},
onPanResponderRelease: (evt, gestureState) => {}
onPanResponderTerminate: (evt, gestureState) => {}
});
}
  • 使用

    1
    2
    3
    render() {
    return <View {...this.panResponder.panHandlers}} ></View>
    }

使用手势系统实现图片拖拽

1、实现长按/释放改变图片样式

  1. 声明一个状态控制变量

    1
    2
    3
    4
    this.state = {
    //是否开始拖动
    isMove: false
    };
  2. 设置手势系统

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    componentWillMount(evt, gestureState) {
    let self = this;
    //手势创建
    this._panResponder = PanResponder.create({
    onStartShouldSetPanResponder: (evt, gestureState) => {
    //当长按1秒之后使得isMove为真,开启拖拽状态
    window.homeTimer = setTimeout(()=>{
    console.warn('定时开启');
    self.setState((state)=>{
    return{
    isMove: true
    }
    });
    },1000);
    return true;
    },
    onMoveShouldSetPanResponder: (evt, gestureState) => true,
    onPanResponderGrant: (evt, gestureState) => {},
    onPanResponderMove: (evt, gestureState) => {
    if(self.state.isMove){
    console.warn('正在移动');
    }
    },
    onPanResponderRelease: (evt, gestureState) => {
    //释放时,立刻清除定时器,使isMove不为真
    clearTimeout(window.homeTimer);
    //开启拖拽状态后,释放时关闭拖拽状态
    self.setState((state)=>{
    return{
    isMove: false
    }
    });
    }
    });
    }
  1. 利用this.state.isMove来控制图片状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
       render() {
    return (
    <View style={styles.root}>
    <View {...this._panResponder.panHandlers} ref='refImage' style={this.state.isMove ? styles.moveView : styles.noMoveView}>
    <Image style={{width: '100%',height: '100%'}} source={require('../../assets/images/test.png')}></Image>
    </View>
    </View>
    );
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const styles = StyleSheet.create({
noMoveView: {
width: 200,
height: 200,
},
noMoveView: {
width: 200,
height: 200,
/*绝对定位后,可使其脱离文档流,通过控制left,top来达到移动的效果*/
position: 'absolute',
left: 0,
top: 0,
zIndex: 999
}
});

效果:

2、实现图片移动跟随

  1. 在手指触碰到屏幕时,记录当前触碰位置(在onPanResponderGrant中记录)

    1
    2
    3
    4
    5
    6
    7
    8
    onPanResponderGrant: (evt, gestureState) => {
    // pageY是相对于根节点的位置,locationY是相对于元素自己
    const { pageX, pageY, locationY, locationX } = evt.nativeEvent;
    // 保存当前正确点击元素的位置,为了后面移动元素
    this.preY = pageY - locationY;
    // 保存当前正确点击元素的位置,为了后面移动元素
    this.preX = pageX - locationX;
    },
  2. 移动时实时改变当前元素绝对定位的lefttop属性值(在onPanResponderMove中改变)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    onPanResponderMove: (evt, gestureState) => {
    //若处于移动状态下
    if (self.state.isMove) {
    let top = this.preY + gestureState.dy;
    let left = this.preX + gestureState.dx;
    //获取当前操作对象元素
    let item = this.refs.refImage;
    //设置样式
    item.setNativeProps({
    style: { top: top, left: left },
    });
    }
    },

3、实时监控图片位置,当满足位置条件并且手指离开屏幕时触发方法

onPanResponderRelease

1
2
3
4
5
6
7
8
onPanResponderRelease: (evt, gestureState) => {
clearTimeout(window.homeTimer);
self.setState((state) => {
return {
isMove: false
}
});
}

结语

React-Native中的手势系统

​ 优点:功能强大,可以借助此工具完成多指手指,滑动退出等功能

缺点:无法确定是哪一个元素触发事件,当点击某元素时,是无法直接获取该元素,需要通过手动计算得到

我的疑问

在手势系统中的通过evt.nativeEvent.target拿到的id值有何作用?

最后更新: 2019年09月03日 10:44

原始链接: https://HowlCN.github.io/2019/02/07/React-native中的手势系统/

× 请我吃糖~
打赏二维码