react-native(RN)实现可拖动的悬浮按钮

一、PanResponder相关知识点

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

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

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

onPanResponderMove: (event, gestureState) => {}

原生事件是指由以下字段组成的合成触摸事件:

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,
      
      //开启点击手势响应是否劫持 true:不传递给子view false:传递给子view
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      
      //开启移动手势响应
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      
      //开启移动手势响应是否劫持 true:不传递给子view false:传递给子view
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

      //手指触碰屏幕那一刻触发 成为激活状态。
      onPanResponderGrant: (evt, gestureState) => {
        // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!

        // gestureState.{x,y} 现在会被设置为0
      },
      
      //表示手指按下时,成功申请为事件响应者的回调。
      onPanResponderStart: (evt, gestureState) => {
        // 表示申请成功,你成为了事件的响应者,这个时候开始,组件就进入了激活状态。
      },

      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} />
    );
  },

二、实现方式

① 官网Demo

链接 ===> https://reactnative.cn/docs/next/panresponder
结合Animated和PanResponder实现,效果如下:

在这里插入图片描述

② PanResponder实现Demo

在这里插入图片描述
代码如下

// dx 和 dy:从触摸操作开始到现在的累积横向/纵向路程
//
// moveX 和 moveY:最近一次移动时的屏幕横/纵坐标
//
// numberActiveTouches:当前在屏幕上的有效触摸点的数量
//
// stated:和之前一样,用来识别手指的ID
//
// vx 和 vy:当前横向/纵向移动的速度
//
// x0 和 y0:当触摸操作开始时组件相对于屏幕的横/纵坐标

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


export default class TouchStartAndRelease extends PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            backgroundColor: 'red',
            marginTop: 100,
            marginLeft: 100,
        };
        this.lastX = this.state.marginLeft;
        this.lastY = this.state.marginTop;
    }

    componentWillMount() {
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => {
                return true;
            },
            onMoveShouldSetPanResponder: (evt, gestureState) => {
                return true;
            },
            onPanResponderGrant: (evt, gestureState) => {
                this._highlight();
            },
            onPanResponderMove: (evt, gestureState) => {
                console.log(`gestureState.dx : ${gestureState.dx}   gestureState.dy : ${gestureState.dy}`);
                this.setState({
                    marginLeft: this.lastX + gestureState.dx,
                    marginTop: this.lastY + gestureState.dy,
                });
            },
            onPanResponderRelease: (evt, gestureState) => {
                this._unhighlight();
                this.lastX = this.state.marginLeft;
                this.lastY = this.state.marginTop;
            },
            onPanResponderTerminate: (evt, gestureState) => {
            },
        });
    }

    _unhighlight() {
        this.setState({
            backgroundColor: 'red',
        });
    }

    _highlight() {
        this.setState({
            backgroundColor: 'blue',
        });
    }

    render() {
        return (
            <View style={styles.container}>
                <View style={[styles.redView,
                    {
                        backgroundColor: this.state.backgroundColor,
                        marginTop: this.state.marginTop,
                        marginLeft: this.state.marginLeft,
                    }
                ]}
                      {...this._panResponder.panHandlers}
                />
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    redView: {
        width: 100,
        height: 100,
    }
});

三、拓展

手势移动与点击事件冲突解决办法

参考 react-native 手势移动与点击事件冲突解决办法

代码如下

        this._panResponder = PanResponder.create({

            onPanResponderGrant: (evt, gestureState) => {
                //触摸开始
                this.DragMove = false;
            },

            onPanResponderMove: (evt, gestureState) => {
                /*
                moveX - 最近一次移动时的屏幕横坐标
                moveY - 最近一次移动时的屏幕纵坐标
                x0 - 当响应器产生时的屏幕坐标
                y0 - 当响应器产生时的屏幕坐标
                dx - 从触摸操作开始时的累计横向路程
                dy - 从触摸操作开始时的累计纵向路程
                vx - 当前的横向移动速度
                vy - 当前的纵向移动速度
                */
                if (gestureState.dx !== 0 || gestureState.dy !== 0) {
                    this.DragMove = true;
                }
            },
            onPanResponderRelease: (evt, gestureState) => {
                //触摸释放
                
                if(this.DragMove){//拖动
                    this.lastX = this.state.DragMarginLeft;
                    this.lastY = this.state.DragMarginTop;
                }else{//点击
                    console.log("onPanResponderRelease OnClick")
                }
            },
        });

四、参考

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Crazy程序猿2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值