移動設備上的手勢識別要比在 web 上覆雜得多。用戶的一次觸摸操做的真實意圖是什麼,App 要通過好幾個階段才能判斷。好比 App 須要判斷用戶的觸摸究竟是在滾動頁面,仍是滑動一個 widget,或者只是一個單純的點擊。甚至隨着持續時間的不一樣,這些操做還會轉化。此外,還有多點同時觸控的狀況。react
手勢響應系統可使組件在不關心父組件或子組件的前提下自行處理觸摸交互。web
做爲與用戶交互的第一層,觸摸事件直接影響着用戶行爲體驗。在Android 和 iOS 平臺設備中,對於觸摸機制作了很是完善的封裝,可以很方便的幫助開發者處理基本的觸摸行爲操做,原平生臺經過註冊Listener的方式能夠輕鬆的實現單擊,雙擊等操做。在RN中一樣提供了與Native觸摸事件映射一致的處理方式,方便React Native開發者處理觸摸行爲,定義觸摸操做。react-native
RN系統中爲咱們提供了TouchableHighlight 與 Touchable 系列組件,不懂得本身找度娘就好了。數組
一個View只要實現了正確的協商方法,就能夠成爲觸摸事件的響應者。經過如下兩種方法去「詢問」一個View是否願意成爲響應者:ide
View.props.onStartShouldSetResponder: (evt) => true,在用戶開始觸摸的時候(手指剛剛接觸屏幕的瞬間),是否願意成爲響應者;函數
View.props.onMoveShouldSetResponder: (evt) => true, 若是View不是響應者,那麼在每個觸摸點開始移動(沒有停下也沒有離開屏幕)時再詢問一次:是否願意成爲響應者?佈局
若是 View 返回 true,並開始嘗試成爲響應者,那麼會觸發下列事件之一:this
View.props.onResponderGrant: (evt) => {} View如今要開始響應觸摸事件了,這也是須要作高亮的時候,使用戶知道他點了哪裏。spa
View.props.onResponderReject: (evt) => {}響應者如今「另有其人」並且暫時不會「放權」,請另做安排。 操作系統
若是 View 已經開始響應觸摸事件了,那麼下列這些處理函數會被一一調用:
View.props.onResponderMove: (evt) => {}
- 用戶正在屏幕上移動手指時(沒有停下也沒有離開屏幕)。
View.props.onResponderRelease: (evt) => {}
- 觸摸操做結束時觸發,好比"touchUp"(手指擡起離開屏幕)。
View.props.onResponderTerminationRequest: (evt) => true
- 有其餘組件請求接替響應者,當前的 View 是否「放權」?返回 true 的話則釋放響應者權力。
View.props.onResponderTerminate: (evt) => {}
- 響應者權力已經交出。這多是因爲其餘 View 經過onResponderTerminationRequest
請求的,也多是由操做系統強制奪權(好比 iOS 上的控制中心或是通知中心)。
其中evt
是一個合成事件,它包含如下結構:
nativeEvent
-
changedTouches
- 在上一次事件以後,全部發生變化的觸摸事件的數組集合(即上一次事件後,全部移動過的觸摸點)
- identifier
- 觸摸點的 ID
- locationX
- 觸摸點相對於當前元素的橫座標
- locationY
- 觸摸點相對於當前元素的縱座標
- pageX
- 觸摸點相對於根元素的橫座標
- pageY
- 觸摸點相對於根元素的縱座標
- target
- 觸摸點所在的元素 ID
- timestamp
- 觸摸事件的時間戳,可用於移動速度的計算
- touches
- 當前屏幕上的全部觸摸點的集合
onStartShouldSetResponder
與onMoveShouldSetResponder
是以冒泡的形式調用的,即嵌套最深的節點最早調用。這意味着當多個 View 同時在*ShouldSetResponder
中返回 true 時,最底層的 View 將優先「奪權」。在多數狀況下這並無什麼問題,由於這樣能夠確保全部控件和按鈕是可用的。
可是有些時候,某個父 View 會但願能先成爲響應者。咱們能夠利用「捕獲期」來解決這一需求。響應系統在從最底層的組件開始冒泡以前,會首先執行一個「捕獲期」,在此期間會觸發on*ShouldSetResponderCapture
系列事件。所以,若是某個父 View 想要在觸摸操做開始時阻止子組件成爲響應者,那就應該處理onStartShouldSetResponderCapture
事件並返回 true 值。
View.props.onStartShouldSetResponderCapture: (evt) => true, View.props.onMoveShouldSetResponderCapture: (evt) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => { // 在觸摸事件 開始,RN父佈局組件會回調 onStartShouldSetResponderCapture,詢問是否要攔截事件,本身接收處理, true 表示攔截。 console.log('onStartShouldSetPanResponderCapture') console.log(gestureState.dx) return false; }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { // 在觸摸 滑動 事件時,RN父佈局組件會回調 onMoveShouldSetResponderCapture,詢問是否要攔截事件,本身接收處理, true 表示攔截。 console.log('onMoveShouldSetPanResponderCapture') console.log(gestureState) return false; }, onStartShouldSetPanResponder: (evt, gestureState) => { /** * 在手指觸摸開始時申請成爲響應者 */ console.log('onStartShouldSetPanResponder') console.log(gestureState) return true; }, onMoveShouldSetPanResponder: (evt, gestureState) => { /** * 在手指在屏幕移動時申請成爲響應者 */ console.log('onMoveShouldSetPanResponder') console.log(gestureState) return true; }, onPanResponderGrant: (evt, gestureState) => { //開始手勢操做。給用戶一些視覺反饋,讓他們知道發生了什麼事情! /** * 申請成功,組件成爲了事件處理響應者,這時組件就開始接收後序的觸摸事件輸入。 * 通常狀況下,這時開始,組件進入了激活狀態,並進行一些事件處理或者手勢識別的初始化 */ console.log('onPanResponderGrant') console.log(gestureState) }, onPanResponderReject: (evt, gestureState) => { /** * 表示申請失敗了,這意味者其餘組件正在進行事件處理, * 而且它不想放棄事件處理,因此你的申請被拒絕了,後續輸入事件不會傳遞給本組件進行處理。 */ console.log('onPanResponderReject') }, onPanResponderStart:(evt, gestureState) => { /** * 表示手指按下時,成功申請爲事件響應者的回調 */ console.log('onPanResponderStart') console.log(gestureState) }, onPanResponderMove:(evt, gestureState) => { //最近一次的移動距離爲gestureState.move{X,Y} // 從成爲響應者開始時的累計手勢移動距離爲gestureState.d{x,y} /** * 表示觸摸手指移動的事件,這個回調可能很是頻繁,因此這個回調函數的內容須要儘可能簡單 */ console.log('onPanResponderMove') console.log(gestureState) }, onPanResponderRelease:(evt, gestureState) => { //用戶放開了全部的觸摸點,且此時視圖已經成爲了響應者。 //通常來講這個意味着一個手勢操做已經完成了。 /** * 表示觸摸完成(touchUp)的時候的回調,表示用戶完成了本次的觸摸交互,這裏應該完成手勢識別的處理, * 這之後,組件再也不是事件響應者,組件取消激活 */ console.log('onPanResponderRelease') console.log(gestureState) }, onPanResponderEnd:(evt, gestureState) => { /** * 組件結束事件響應的回調 */ console.log('onPanResponderEnd') console.log(gestureState) }, onResponderTerminationRequest: (evt) => { /** * 當其餘組件申請成爲響應者時,詢問你是否能夠釋放響應者角色讓給其餘組件 */ console.log('onResponderTerminationRequest'); return true; }, onResponderTerminate: (evt) => { /** * 若是 onResponderTerminationRequest 回調函數返回爲 true, * 則表示贊成釋放響應者角色,同時會回調以下函數,通知組件事件響應處理被終止 * 這多是因爲其餘View經過onResponderTerminationRequest請求的,也多是由操做系統強制奪權(好比iOS上的控制中心或是通知中心)。 */ console.log('onResponderTerminate'); }
註釋已經說明了,很少作闡述。案例以下:
/** * PanResponder 觸摸事件 * @export * @class PanResponderView * @extends {Component} */ import React, { Component } from 'react'; import { View, Text, StyleSheet, PanResponder, } from 'react-native'; export default class HomeScreen extends Component { constructor(props) { super(props) this.panResponder={} } componentWillMount() { this.panResponder = PanResponder.create({ onStartShouldSetPanResponderCapture: (evt, gestureState) => { // 在觸摸事件 開始,RN父佈局組件會回調 onStartShouldSetResponderCapture,詢問是否要攔截事件,本身接收處理, true 表示攔截。 console.log('onStartShouldSetPanResponderCapture') console.log(gestureState.dx) return false; }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { // 在觸摸 滑動 事件時,RN父佈局組件會回調 onMoveShouldSetResponderCapture,詢問是否要攔截事件,本身接收處理, true 表示攔截。 console.log('onMoveShouldSetPanResponderCapture') console.log(gestureState) return false; }, onStartShouldSetPanResponder: (evt, gestureState) => { /** * 在手指觸摸開始時申請成爲響應者 */ console.log('onStartShouldSetPanResponder') console.log(gestureState) return true; }, onMoveShouldSetPanResponder: (evt, gestureState) => { /** * 在手指在屏幕移動時申請成爲響應者 */ console.log('onMoveShouldSetPanResponder') console.log(gestureState) return true; }, onPanResponderGrant: (evt, gestureState) => { //開始手勢操做。給用戶一些視覺反饋,讓他們知道發生了什麼事情! /** * 申請成功,組件成爲了事件處理響應者,這時組件就開始接收後序的觸摸事件輸入。 * 通常狀況下,這時開始,組件進入了激活狀態,並進行一些事件處理或者手勢識別的初始化 */ console.log('onPanResponderGrant') console.log(gestureState) }, onPanResponderReject: (evt, gestureState) => { /** * 表示申請失敗了,這意味者其餘組件正在進行事件處理, * 而且它不想放棄事件處理,因此你的申請被拒絕了,後續輸入事件不會傳遞給本組件進行處理。 */ console.log('onPanResponderReject') }, onPanResponderStart:(evt, gestureState) => { /** * 表示手指按下時,成功申請爲事件響應者的回調 */ console.log('onPanResponderStart') console.log(gestureState) }, onPanResponderMove:(evt, gestureState) => { //最近一次的移動距離爲gestureState.move{X,Y} // 從成爲響應者開始時的累計手勢移動距離爲gestureState.d{x,y} /** * 表示觸摸手指移動的事件,這個回調可能很是頻繁,因此這個回調函數的內容須要儘可能簡單 */ console.log('onPanResponderMove') console.log(gestureState) }, onPanResponderRelease:(evt, gestureState) => { //用戶放開了全部的觸摸點,且此時視圖已經成爲了響應者。 //通常來講這個意味着一個手勢操做已經完成了。 /** * 表示觸摸完成(touchUp)的時候的回調,表示用戶完成了本次的觸摸交互,這裏應該完成手勢識別的處理, * 這之後,組件再也不是事件響應者,組件取消激活 */ console.log('onPanResponderRelease') console.log(gestureState) }, onPanResponderEnd:(evt, gestureState) => { /** * 組件結束事件響應的回調 */ console.log('onPanResponderEnd') console.log(gestureState) }, onResponderTerminationRequest: (evt) => { /** * 當其餘組件申請成爲響應者時,詢問你是否能夠釋放響應者角色讓給其餘組件 */ console.log('onResponderTerminationRequest'); return true; }, onResponderTerminate: (evt) => { /** * 若是 onResponderTerminationRequest 回調函數返回爲 true, * 則表示贊成釋放響應者角色,同時會回調以下函數,通知組件事件響應處理被終止 * 這多是因爲其餘View經過onResponderTerminationRequest請求的,也多是由操做系統強制奪權(好比iOS上的控制中心或是通知中心)。 */ console.log('onResponderTerminate'); } }); } render() { return ( <View {...this.panResponder.panHandlers } style={ styles.container }> </View> ) } } const styles = StyleSheet.create({ container: { width: 100, height: 100, borderRadius: 50, backgroundColor: '#87CEFA' }, btn: { width: 100, height: 60, alignItems: 'center', justifyContent: 'center', backgroundColor: '#ff5511' }, btnText: { color: 'white' } });
運行效果: