[React-Native PanResponder拖拽实现]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/iOSTianNan/article/details/85095788

1.PanResnponder相关Api

    componentWillMount() {
        this.panResponder = PanResponder.create({

            /***************** 要求成为响应者 *****************/
            // 单机手势是否可以成为响应者
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            // 移动手势是否可以成为响应者
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            // 拦截子组件的单击手势传递,是否拦截
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            // 拦截子组件的移动手势传递,是否拦截
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,


            /***************** 响应者事件回调处理 *****************/
            // 单击手势监听回调
            onPanResponderGrant: (e, gestureState) => {
                console.log('onPanResponderGrant==>' + '单击手势申请成功,开始处理手势')
                this._onPanResponderGrant(e)
            },
            // 移动手势监听回调
            onPanResponderMove: (e, gestureState) => {
                console.log('onPanResponderMove==>' + '移动手势申请成功,开始处理手势' + `${gestureState}`)
                this._onPanResponderMove(e, gestureState);
            },
            // 手势动作结束回调
            onPanResponderEnd: (evt, gestureState) => {
                console.log('onPanResponderEnd==>' + '手势操作完成了,用户离开')
                this._onPanResponderEnd(evt)
            },
            // 手势释放, 响应者释放回调
            onPanResponderRelease: (e) => {
                // 用户放开了所有的触摸点,且此时视图已经成为了响应者。
                // 一般来说这意味着一个手势操作已经成功完成。
                console.log('onPanResponderRelease==>' + '放开了触摸,手势结束')
            },
            // 手势申请失败,未成为响应者的回调
            onResponderReject: (e) => {
                // 申请失败,其他组件未释放响应者
                console.log('onResponderReject==>' + '响应者申请失败')
            },

            // 当前手势被强制取消的回调
            onPanResponderTerminate: (e) => {
                // 另一个组件已经成为了新的响应者,所以当前手势将被取消
                console.log('onPanResponderTerminate==>' + '由于某些原因(系统等),所以当前手势将被取消')
            },
            onShouldBlockNativeResponder: (evt, gestureState) => {
                // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
                // 默认返回true。目前暂时只支持android。
                return true;
            },
        })
    }

一般使用手势的话,以上Api基本可以实现

在这里插入图片描述

2. 使用绝对布局方式,完成拖拽小球的实现

render() {
        return (
            <View style={{flex: 1}}>

                {this.renderNomalNavigationBar('触摸响应页')}

                <TouchableOpacity
                    onPress={() => this.enterPenresnponderShow()}
                    style={{backgroundColor: '#00ffee', width: pxdp.width, height: pxdp.fixHeight(50)}}
                />
                <View style={{
                    backgroundColor: this.state.isHeightShow ? '#ffddff' : '#ff0fa0',
                    borderColor: '#ff0',
                    borderWidth: 1,
                    borderRadius: pxdp.fixWidth(25),
                    width: pxdp.fixWidth(50),
                    height: pxdp.fixWidth(50),

                    position: 'absolute',
                    left: this.state.marginLeft,
                    top: this.state.marginTop,
                }} {...this.panResponder.panHandlers}
                />
            </View>


        )
    }


    /**************************************** 逻辑处理 ****************************************/
    /*
    * 备注: 拖动小圆点的实现方法 - 使用绝对定位
    * 对小圆点设置绝对布局,
    * 通过触发开始的pageXY与moveXY的差值
    * 来变更top,left的大小,
    * position一定要为 absolute
    * */

    // 单点手势开始
    _onPanResponderGrant(e) {

        //1. 开始触发时,获取触摸点绝对位置
        this.touchX = e.nativeEvent.locationX;
        this.touchY = e.nativeEvent.locationY;

    }

    // 移动手势处理
    _onPanResponderMove(evt, g) {
        console.log("        ");
        console.log(evt.nativeEvent);
        console.log('=================')
        console.log(g);

        //2. 根据触发点计算真实的左侧,顶侧位移变化
        let realMarginLeft = g.moveX - this.touchX;
        let realMarginTop = g.moveY - this.touchY;

        this.setState({
            marginLeft: realMarginLeft,
            marginTop: realMarginTop
        })


    }

    // 手势结束
    _onPanResponderEnd(evt) {
        let pageX = evt.nativeEvent.pageX;

        this.setState({
            isHeightShow: false,
        });

        if (pageX <= pxdp.width / 4) {
            pageX = 0;
            this.setState({
                marginLeft: pageX,
            })
        }
        if (pageX >= pxdp.width * 3 / 4) {
            pageX = pxdp.width - pxdp.fixWidth(50);
            this.setState({
                marginLeft: pageX,
            })
        }
    }

以上是通过绝对布局的方式来实现了一个拖拽的小球,但不是所有地方都应该使用绝对布局来实现拖拽,有些地方不应该使用绝对布局,并且也难以实现,失去了Flex弹性盒子模型的优势

3.通过相对布局,实现列表的拖拽

在这里插入图片描述

 /**************************************** 渲染 ****************************************/
    render() {
        return (
            <View style={{
                width: pxdp.width, height: this.cellHeight, borderWidth: 1, borderColor: '#090',
                borderRadius: 10, top: this.state.topOY, backgroundColor: '#00ddea'
            }} {...this.panResponder.panHandlers}>
                <Text>{this.props.title}</Text>
            </View>
        )
    }

    /**************************************** 逻辑 ****************************************/


    //拖拽触发
    _onPanResponderGrant(e, g) {
        console.log('触发了: 点击Grant')
        // console.log(e.nativeEvent.locationY);
        // console.log(e.nativeEvent.pageY);

        //1. 获取触摸点的绝对位置
        this.touchY = e.nativeEvent.pageY;
    }

    //拖拽移动
    _onPanResponderMove(e, g) {
        console.log('触发了: 移动Move');
        // console.log(e.nativeEvent);
        // console.log(g);

        //2. 计算top真正的移动距离

        //2.1 获取移动过程中,在Y轴上移动了多少(该值不能直接作为top变量,否则不能实现拖拽精准移动)
        let changeY = g.moveY - this.touchY;
        //2.2 根据本次的Y轴移动及上次Y轴移动,计算本次真正的Y轴移动变化量
        let realChangeY = changeY - this.lastChangeY;
        //2.3 计算移动中, 触摸点真正的移动了多少距离
        this.realChangeY = this.realChangeY + realChangeY;
        //2.4 同理,top相对触摸点,也移动了同样的距离
        this.setState({
            topOY: this.realChangeY
        });
        //2.5 记录上次移动的距离
        this.lastChangeY = changeY;
    }

    //结束拖拽
    _onPanResponderRelease(e, g) {
        console.log('释放了: 离开屏幕Release')
        //3. 结束触发后, 清除本次交互时,记录的Y轴移动大小,避免影响下次再次交互
        this.lastChangeY = 0;
    }

总结一下

PanResponder的交互过程中,会返回我们nativeEvent及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 - 当前在屏幕上的有效触摸点的数量

常用的有pageXY, 是相对于屏幕的触摸点
locationXY是相对于组件触摸点位置
moveXY则是每次移动过程中反馈出的上一次移动距离值
由于FLex弹性盒子模型的使用, 我们不太能用坐标点来改变组件的位置,因此,我们可以改变组件的top/left等属性
但是,由于直接使用moveXY等值去修改top/left等属性,会造成在刚触摸移动的时候,top/left等属性得到了触摸点的左边,会造成一定程度位置混乱偏移,因此,需要手动来计算top/left的变化情况, 而绝对布局下,这个问题则相对来说比较容易处理

直接使用会出现如下问题

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/iOSTianNan/article/details/85095788
今日推荐