溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React Native觸摸事件處理是怎樣的

發(fā)布時間:2021-12-31 16:16:28 來源:億速云 閱讀:124 作者:柒染 欄目:移動開發(fā)

本篇文章為大家展示了React Native觸摸事件處理是怎樣的,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

觸控是移動設備的核心功能,也移動應用交互的基礎,Android 和 iOS 各自都有完善的觸摸事件處理機制。React Native(以下簡稱  RN)提供了一套統(tǒng)一的處理方式,能夠方便的處理界面中組件的觸摸事件、用戶手勢等。下面嘗試介紹 RN 中觸摸事件處理。

1. RN 基本觸摸組件

RN 的組件除了 Text,其他組件默認是不支持點擊事件,也不能響應基本觸摸事件,所以 RN  中提供了幾個直接處理響應事件的組件,基本上能夠滿大部分的點擊處理需求TouchableHighlight, TouchableNativeFeedback,  TouchableOpacity 和 TouchableWithoutFeedback。因為這幾個組件的功能和使用方法基本類似,只是 Touch  的反饋效果不一樣,所以一般我們用 Touchable** 代替。Touchable** 有如下幾個回調方法:

  • onPressIn:點擊開始;

  • onPressOut:點擊結束或者離開;

  • onPress:單擊事件回調;

  • onLongPress:長按事件回調。

它們的基本使用方法如下,這里以 TouchableHighlight 為例:

<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>

RN 中提供的觸摸組件使用非常簡單,可以參考 官方文檔,這里也不做詳細的介紹了。下面主要介紹用戶觸摸事件處理。

2. 單組件觸摸事件處理

我們知道,RN  的組件默認不進行處理觸摸事件。組件要處理觸摸事件,首先要“申請”成為摸事件的響應者(Responder),完成事件處理以后,會釋放響應者的角色。一個觸摸事件處理周期,是從用戶手指按下屏幕,到用戶抬起手指抬起結束,這是用戶的一次完整觸摸操作。

單個組件的單次操作交互處理的生命周期如下:

React Native觸摸事件處理是怎樣的

我們來詳細分析一下事件處理的生命周期,在整個事件處理的過程中,組件有可能處于兩種身份中的一種,并且可以相互切換:非事件響應者和事件響應者。

非事件響應者

默認情況下,觸摸事件輸入不會直接傳遞給組件,不能進行事件響應處理,也就是非事件響應者。如果組件要進行觸摸事件處理,首先要申請成為事件響應者,組件有如下兩個屬性可以做這樣的申請:

  • View.props.onStartShouldSetResponder,這個屬性接收一個回調函數(shù),函數(shù)原型是 function(evt):  bool,在觸摸事件開始(touchDown)的時候,RN 會回調此函數(shù),詢問組件是否需要成為事件響應者,接收事件處理,如果返回  true,表示需要成為響應者;

  • View.props.onMoveShouldSetResponder,它和前一個屬性類似,不過這是觸摸是進行過程中(touchMove),RN  詢問組件是否要成為響應者,返回 true 表示是。

假如組件通過上面的方法返回了 true,表示發(fā)出了申請要成為事件響應者請求,想要接收后續(xù)的事件輸入。因為同一時刻,只能有一個事件處理響應者,RN  還需要協(xié)調所有組件的事件處理請求,所以不是每個組件申請都能成功,RN 通過如下兩個回調來通知告訴組件它的申請結果,:

  • View.props.onResponderGrant: (evt) =>  {}:表示申請成功,組件成為了事件處理響應者,這時組件就開始接收后序的觸摸事件輸入。一般情況下,這時開始,組件進入了激活狀態(tài),并進行一些事件處理或者手勢識別的初始化。

  • View.props.onResponderReject: (evt) =>  {}:表示申請失敗了,這意味者其他組件正在進行事件處理,并且它不想放棄事件處理,所以你的申請被拒絕了,后續(xù)輸入事件不會傳遞給本組件進行處理。

事件響應者

如果通過上面的步驟,組件申請成為了事件響應者,后續(xù)的事件輸入都會通過回調函數(shù)通知到組件,如下:

  • View.props.onResponderStart: (evt) => {}:表示手指按下時,成功申請為事件響應者的回調;

  • View.props.onResponderMove: (evt) =>  {}:表示觸摸手指移動的事件,這個回調可能非常頻繁,所以這個回調函數(shù)的內容需要盡量簡單;

  • View.props.onResponderRelease: (evt) =>  {}:表示觸摸完成(touchUp)的時候的回調,表示用戶完成了本次的觸摸交互,這里應該完成手勢識別的處理,這以后,組件不再是事件響應者,組件取消激活。

  • View.props.onResponderEnd: (evt) => {}:表示組件結束事件響應的回調。

從前面的圖中也看到,在組件成為事件響應者期間,其他組件也可能會申請觸摸事件處理。此時 RN  會通過回調詢問你是否可以釋放響應者角色讓給其他組件?;卣{如下:

View.props.onResponderTerminationRequest: (evt) => bool

如果回調函數(shù)返回為 true,則表示同意釋放響應者角色,同時會回調如下函數(shù),通知組件事件響應處理被終止了:

View.props.onResponderTerminate: (evt) => {}

這個回調也會發(fā)生在系統(tǒng)直接終止組件的事件處理,例如用戶在觸摸操作過程中,突然來電話的情況。

事件數(shù)據(jù)結構

從前面我們看到,觸摸事件處理的回調都有一個 evt 參數(shù),包含一個觸摸事件數(shù)據(jù) nativeEvent。nativeEvent 的詳細內容如下:

  • identifier:觸摸的 ID,一般對應手指,在多點觸控的時候,用來區(qū)分是哪個手指的觸摸事件;

  • locationX 和 locationY:觸摸點相對組件的位置;

  • pageX 和 pageY:觸摸點相對于屏幕的位置;

  • timestamp:當前觸摸的事件的時間戳,可以用來進行滑動計算;

  • target:接收當前觸摸事件的組件 ID;

  • changedTouches:evt  數(shù)組,從上次回調上報的觸摸事件,到這次上報之間的所有事件數(shù)組。因為用戶觸摸過程中,會產生大量事件,有時候可能沒有及時上報,系統(tǒng)用這種方式批量上報;

  • touches:evt 數(shù)組,多點觸摸的時候,包含當前所有觸摸點的事件。

這些數(shù)據(jù)中,最常用的是 locationX 和 locationY 數(shù)據(jù),需要注意的是,因為這里是 Native  的數(shù)據(jù),所以他們的單位是實際像素。如果要轉換為 RN 中的邏輯單位,可以示使用如下方法:

var pX = evt.nativeEvent.locationX / PixelRatio.get();

3. 嵌套組件事件處理

上一小節(jié)介紹的都是針對單個組件來說,事件處理的流程和機制。但是前面也提到了,當組件需要作為事件處理響應者時,需要通過  onStartShouldSetResponder 或者 onMoveShouldSetResponder 回調返回值為 true  來申請。假如當多個組件嵌套的時候,這兩個回調都返回了 true  的時候,但是同一個只能有一個事件處理響應者,這種情況怎么處理呢?為了便于描述,假設我們的組件布局如下:

React Native觸摸事件處理是怎樣的

在 RN 中,默認情況下使用冒泡機制,響應最深的組件***開始響應,所以前面描述的這種情況,如圖中,如果 A、B、C 三個組件的  on*ShouldSetResponder 都返回為 true,那么只有 C  組件會得到響應成為響應者。這種機制才能保證了界面所有的組件才能得到響應。但是有些情況下,可能父組件可能需要處理事件,而禁止子組件響應。RN  提供了一個劫持機制,也就是在觸摸事件往下傳遞的時候,先詢問父組件是否需要劫持,不給子組件傳遞事件,也就是如下兩個回調:

  • View.props.onStartShouldSetResponderCapture:這個屬性接收一個回調函數(shù),函數(shù)原型是 function(evt):  bool,在觸摸事件開始(touchDown)的時候,RN 容器組件會回調此函數(shù),詢問組件是否要劫持事件響應者設置,自己接收事件處理,如果返回  true,表示需要劫持;

  • View.props.onMoveShouldSetResponderCapture:此函數(shù)類似,不過是在觸摸移動事件(touchMove)詢問容器組件是否劫持。

可以把這種劫持機制看成是一種下沉機制,與上面的冒泡機制對應,我們可以總結 RN 事件處理流程如下圖:

React Native觸摸事件處理是怎樣的

注,圖中的 * 表示可以為 Start 或者 Move,例如 on*ShouldSetResponderCapture 表示  onStartShouldSetResponderCapture 或者 onMoveShouldSetResponderCapture,其他的類似。

觸摸事件開始,首先調用 A 組件的 onStartShouldSetResponderCapture,若此回調返回 false,則按照圖傳遞到 B  組件,然后調用 B 組件 onStartShouldSetResponderCapture,若返回 true,則事件不再傳遞給 C 組件,直接調用本組件的  onResponderStart,則 B 組件就成為事件響應者,后續(xù)事件直接傳遞給它。其他的分析類似。

注意到,圖中還有 onTouchStart/onTouchStop  回調,這個回調并不受響應者的影響,在范圍內的組件都會回調此函數(shù),而且調用順序是從最深層組件到最上層組件。

4. 手勢識別

前面只是介紹了簡單的觸摸事件處理機制及其使用方法,其實連續(xù)的觸摸事件,可以組成一些更高級手勢,例如我們最常見的滑動屏幕內容,雙指縮放(Pinch)或者旋轉圖片都是通過手勢識別完成的。

因為有些手勢是很常用的,RN 也提供了內置的手勢識別庫  PanResponder,它封裝了上面的事件回調函數(shù),對觸摸事件數(shù)據(jù)進行加工,完成滑動手勢識別,向我們提供更加高級有意義的接口,如下:

  • onMoveShouldSetPanResponder: (e, gestureState) => bool

  • onMoveShouldSetPanResponderCapture: (e, gestureState) => bool

  • onStartShouldSetPanResponder: (e, gestureState) => bool

  • onStartShouldSetPanResponderCapture: (e, gestureState) => bool

  • onPanResponderReject: (e, gestureState) => {&hellip;}

  • onPanResponderGrant: (e, gestureState) => {&hellip;}

  • onPanResponderStart: (e, gestureState) => {&hellip;}

  • onPanResponderEnd: (e, gestureState) => {&hellip;}

  • onPanResponderRelease: (e, gestureState) => {&hellip;}

  • onPanResponderMove: (e, gestureState) => {&hellip;}

  • onPanResponderTerminate: (e, gestureState) => {&hellip;}

  • onPanResponderTerminationRequest: (e, gestureState) => {&hellip;}

  • onShouldBlockNativeResponder: (e, gestureState) => bool

可以看到,這些接口與前面接收的基礎回調基本上是一一對應的,其功能也是類似,這里就不再贅述。這里有一個特別的回調  onShouldBlockNativeResponder 表示是否用 Native 平臺的事件處理,默認是禁用的,全部使用 JS  中的事件處理,注意此函數(shù)目前只能在 Android 平臺上使用。不過這里回調函數(shù)都有一個新的參數(shù)  gestureState,這是與滑動相關的數(shù)據(jù),是對基本觸摸數(shù)據(jù)的分析處理,它的內容如下:

  • stateID:滑動手勢的 ID,在一次完整的交互中此 ID 保持不變;

  • moveX 和 moveY:自上次回調,手勢移動距離;

  • x0 和 y0:滑動手勢識別開始的時候的在屏幕中的坐標;

  • dx 和 dy:從手勢開始時,到當前回調是移動距離;

  • vx 和 vy:當前手勢移動的速度;

  • numberActiveTouches:當期觸摸手指數(shù)量。

下面介紹一個簡單的實例,本例實現(xiàn)可以使用手指拖動界面的圓形控件,使用實例如下:

import React from 'react';   import {     AppRegistry,   PanResponder,   StyleSheet,   View,   processColor, } from 'react-native';   var CIRCLE_SIZE = 80;   var CIRCLE_COLOR = 'blue';   var CIRCLE_HIGHLIGHT_COLOR = 'green';   var PanResponderExample = React.createClass({     statics: {     title: 'PanResponder Sample',     description: 'Shows the use of PanResponder to provide basic gesture handling.',   },     _panResponder: {},   _previousLeft: 0,   _previousTop: 0,   _circleStyles: {},   circle: (null : ?{ setNativeProps(props: Object): void }),     componentWillMount: function() {     this._panResponder = PanResponder.create({       onStartShouldSetPanResponder: (evt, gestureState) => true,       onMoveShouldSetPanResponder: (evt, gestureState) => true,       onPanResponderGrant: this._handlePanResponderGrant,       onPanResponderMove: this._handlePanResponderMove,       onPanResponderRelease: this._handlePanResponderEnd,       onPanResponderTerminate: this._handlePanResponderEnd,     });     this._previousLeft = 20;     this._previousTop = 84;     this._circleStyles = {       style: {         left: this._previousLeft,         top: this._previousTop       }     };   },     componentDidMount: function() {     this._updatePosition();   },     render: function() {     return (       <View style={styles.container}>         <View           ref={(circle) => {             this.circle = circle;           }}           style={styles.circle}           {...this._panResponder.panHandlers}         />       </View>     );   },     _highlight: function() {     const circle = this.circle;     circle && circle.setNativeProps({       style: {         backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR)       }     });   },     _unHighlight: function() {     const circle = this.circle;     circle && circle.setNativeProps({       style: {         backgroundColor: processColor(CIRCLE_COLOR)       }     });   },     _updatePosition: function() {     this.circle && this.circle.setNativeProps(this._circleStyles);   },     _handlePanResponderGrant: function(e: Object, gestureState: Object) {     this._highlight();   },   _handlePanResponderMove: function(e: Object, gestureState: Object) {     this._circleStyles.style.left = this._previousLeft + gestureState.dx;     this._circleStyles.style.top = this._previousTop + gestureState.dy;     this._updatePosition();   },   _handlePanResponderEnd: function(e: Object, gestureState: Object) {     this._unHighlight();     this._previousLeft += gestureState.dx;     this._previousTop += gestureState.dy;   }, });   var styles = StyleSheet.create({     circle: {     width: CIRCLE_SIZE,     height: CIRCLE_SIZE,     borderRadius: CIRCLE_SIZE / 2,     backgroundColor: CIRCLE_COLOR,     position: 'absolute',     left: 0,     top: 0,   },   container: {     flex: 1,     paddingTop: 64,   }, });

可見,在 componentWillMount 中創(chuàng)建一個 PanResponder 實例,并設置想好相關的屬性,然后把這個對象設置給 View  的屬性,如下:

<View     {...this._panResponder.panHandlers} />

其余的代碼也比較簡單,這里就不詳述了。

通過上面的介紹,可以看到 RN 中提供了類似 Native 平臺的事件處理機制,所以也可以實現(xiàn)各種的觸摸事件處理,甚至也可以實現(xiàn)復雜的手勢識別。

在嵌套組件的事件處理中,RN 中提供了“冒泡”和“下沉”兩個方向的事件處理,這有點類似于 Android Native 上不久前才支持的  NestedScrolling,這就提供更加強大的事件處理機制。

另外需要注意,因為 RN 的異步通信和執(zhí)行機制,前面描述的所有回調函數(shù)都是在 JS 線程中,并不是 Native 的 UI 線程,而 Native 平臺的  Touch 事件都是在 UI 線程中。所以在 JS 中通過 Touch 或者手勢實現(xiàn)動畫,可能會延遲的問題。

上述內容就是React Native觸摸事件處理是怎樣的,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI