react native 手势识别正确打开姿势

写在开始之前的话,说好的一个星期一篇的牛逼已然破了,破了就破了吧,我们该坚持的东西,还是需要坚持,不是吗?那么开始吧...

Touchable**触摸控件

我们知道在react native中,如果想要设置点击事件的话,并不像android中那样,
可以直接设置View.setOnClickListener,而是需要通过触摸控件来完成添加点击事
件,需要通过以下Touchable**来设置:

TouchableHighlight,
TouchableNativeFeedback,
TouchableOpacity,
TouchableWithoutFeedback

他们的功能和使用方法基本类似,只是在Touch的时候反馈的效果不同,他们有以下几个回调方法:

onPressIn:点击开始
onPressOut:点击结束或离开
onPress:单击事件回调
onLongPress:长按事件回调

基本用法如下:

<TouchableHighlight  
  onPressIn={() => console.log("onPressIn")}
  onPressOut={() => console.log("onPressOut")}
  onPress={() => console.log("onPress")}
  onLongPress={() => console.log("onLongPress")}
  >
  <Image
    style={styles.button}
    source={require('./img/rn_logo.png')} />
</TouchableHighlight>  

用法比较简单,这里不再介绍。下面我们了解一下给不能点击的组件添加点击事件呢?

我们知道普通的组件并不能设置点击事件,那是因为普通的组件,它并不是触摸响应者,而想成为触摸响应者的话就需要你申请。

第一步:申请

想要申请成为触摸着,需要通过下面两个方法:

View.props.onStartShouldSetResponder

这个属性接受一个回调函数,如果返回true就是申请成为触摸事件的响应者。

View.props.onMoveShouldSetResponder

这个属性接受一个回调函数,如果返回true就是申请成为滑动过程中的响应者。

在实际的代码中,我们通过设置PanResponder来申请和监听的触摸事件。

onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

其中

onStartShouldSetPanResponderCapture: (evt, gestureState) => true,

是表示,是否成为事件的劫持者,如果返回是,则不会把事件传递给它的子元素

onStartShouldSetPanResponderCapture: (evt, gestureState) => true,

表示是否成为滑动事件的劫持者,如果返回是,则不会把滑动事件传递给它的子元素。

第二步:开始手势操作

onPanResponderGrant: (evt, gestureState) => {}

第三步:触摸点移动

onPanResponderMove: (evt, gestureState) => {}

第四步:用户放开了所有的触摸点,且此时视图已经成为了响应者

onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {}

第五步:另一个组件已经成为了新的响应者,所以当前手势将被取消

onPanResponderTerminate: (evt, gestureState) => {}

第六步:返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者(暂只支持android)

onShouldBlockNativeResponder: (evt, gestureState) => {
    return true;   
}
图片

在上面的代码中,我们看到,在触摸开始到滑动,在到触摸结束的过程中,会返回两个参数,evtgestureState,下面我们介绍一下这两个参数:

触摸事件的参数--nativeEvent

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 - 当前在屏幕上的有效触摸点的数量

我们看一个具体的例子

效果如图:

image

代码如下:

import React, {PureComponent, Component} from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    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>
            </View>
        );
    }
}

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

});

为了做到图中的组件可以随我们的手指的滑动而更换位置,我们需要实时的设置它的绝对位置,lefttop,这里面用到了两个属性
gestureState.dxgestureState.dx

dx - 从触摸操作开始时的累计横向路程 
dy - 从触摸操作开始时的累计纵向路程

我们通过记录它的初始位置marginLeftmarginTop,然后加上上面的dxdy,也就是它的累积滑动距离,就知道了它的实时位置,然后把它设置给View,就是上面我们想要的效果了。

在日历上添加横向滑动切换月份

这个是一开始我想要实现的效果,这里不弄gif图了,太麻烦。

componentWillMount() {
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => {
                return true;
            },
            onMoveShouldSetPanResponder:  (evt, gestureState) => {
                return true;
            },
            onPanResponderGrant: (evt, gestureState) => {

            },
            onPanResponderMove: (evt, gestureState) => {
                console.log(`gestureState.dx : ${gestureState.dx}   gestureState.dy : ${gestureState.dy}`);
                //上个月
                if(gestureState.dx >100 && !this.state.isLoading){
                    this.setState({isLoading:true});
                    this.prevMonth();
                //下个月
                }else if(gestureState.dx < -100 && !this.state.isLoading){
                    this.setState({isLoading:true});
                    this.nextMonth();
                }
            },
            onPanResponderRelease: (evt, gestureState) => {
                this.setState({isLoading:false});
            },
            onPanResponderTerminate: (evt, gestureState) => {
            },
        });
    }

当横向滑动的距离dx大于100时,就是向右滑动,我们就切换到上个月,其中isLoading用来判断是否正在刷新界面中。
距离dx小于-100时,就是向左滑,我们切换到下个月。

这里是View的代码

 <View style={styles.container}
                  {...this._panResponder.panHandlers}
            >
                <View ref = 'Head' style={styles.calendar_title_layout} onClick={this.datePickerToggle.bind(this)}>
                    <TouchView onPress={this.prevMonth.bind(this)}>
                        <Image id="prevMonth" style={styles.calendar_img} source={up_month} />
                    </TouchView>
                    <Text style={styles.calendar_title}>{this.state.year +'年'+ (this.state.month<10?'0'+this.state.month:this.state.month) +'月'+ (this.state.day<10?'0'+this.state.day:this.state.day) +'日'}</Text>
                    <TouchView onPress={this.nextMonth.bind(this)}>
                        <Image id="nextMonth" style={styles.calendar_img} source={down_month}/>
                    </TouchView>
                </View>

                <CalendarMain {...props}
                              ref = 'CalendarMain'
                              onDatePickListener = {this.onDatePickListener.bind(this)}
                              onChangeDateListener = {this.onChangeDateListener.bind(this)}
                              year={this.state.year}
                              month={this.state.month}
                              day={this.state.day}

                />

</View>

我们通过{...this._panResponder.panHandlers}这句把PanResponder监听设置到想要设置的组件上,貌似不能设置在我们自定义的组件CalendarMain上,我当时试了一下,没有起作用。

学习react native 的手势识别学习了下面的两篇文章,写的都非常好,其中的代码部分是出自下面的第一篇文章,向作者表示感谢。

panResponder详解及Demo

React Native 触摸事件处理详解

Over...

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容

  • 触控是移动设备的核心功能,也移动应用交互的基础,Android 和 iOS 各自都有完善的触摸事件处理机制。Rea...
    街角仰望阅读 4,440评论 0 2
  • 触控是移动设备的核心功能,也移动应用交互的基础,Android 和 iOS 各自都有完善的触摸事件处理机制。Rea...
    reloadRen阅读 4,269评论 0 53
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,969评论 3 119
  • 很珍贵,得到吴导的教练机会。对于教练,我知道是更多用提问问题的方式来帮助被教练者自己找到解决方案,提升行动力和意...
    镜丰阅读 809评论 0 3
  • 我在三月的烟雨里 选一棵表情不太忧郁的树 用她粉红雪白的花来调亮底色 我们 该相聚在 一张明媚的照片里 东风赶不走...
    野马王阅读 326评论 0 1