標籤:

前端知識 | React Native手勢響應淺析

目前手機市場上,全面屏時代已經勢不可擋,為了增大屏幕,一個個物理按鍵已漸漸消失在手機上。那麼,手勢將成為在移動應用開發中一個重要的組成部分,移動設備上手勢識別要比 web 端複雜得多,往往用戶的一個手勢,我們在 APP 上要通過好幾個階段去判斷用戶的真實意圖是什麼,在 ReactNative (以下簡稱 RN)中針對手勢處理也提供了從最基本的點擊手勢到複雜的滑動等一系列解決方案,讓我們一起去看看。

RN基本觸控組件

RN 的組件除了 Text,其他組件默認是不支持點擊事件的,也不能成為一個觸摸事件的響應者。RN 提供了幾個比較直接的處理響應事件的組件,基本上能滿足大部分的點擊事件的處理需求。

TouchableHighlight

TouchableNativeFeedback (僅限 Android 平台)

TouchableOpacity

TouchableWithoutFeedback

這幾個組件的功能和使用方法基本類似,只是就 Touch 的效果反饋上有所差異,他們有如下幾個回調方法:

onPressIn:用戶觸摸開始的時候,也就是手指剛落在 Touch 點擊區域內的時觸發

onPressOut:用戶觸摸結束的時候,也就是手指從 Touch 點擊區域內抬起的時觸發

onPress:用戶完成一次從 onPressIn 到 onPressOut 的過程,且時間很短,即一次快速點擊操作時觸發

onLongPress:用戶觸發 onPressIn 且手指一段時間內沒有抬起時觸發

這裡以 TouchableHighlight 為例,貼一個 Touch 的基本用法:

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

gesture responder system

在 RN 中,響應手勢的基本單位是 responder,具體點說就是最常見的 View 組件。任何的 View 組件都可以成為一個手勢的響應者。其實要把一個普通的 View 組件開發成為一個能響應手勢操作的 responder 很簡單,話不多說,我們舉栗子!

乍一看,WillMount 裡面的這幾個方法名字又長又奇怪,但是等你了解了 RN 手勢響應的流程了之後,記憶這幾個方法就非常簡單了。在我們探索這幾個方法之前,我們首先要記住一個重要的點:

一個 RN 應用中只能存在一個 responder!

一次正常的手勢操作的流程如下所示:

是否響應 Touch 或者 move 手勢->grant(被激活) ->move->release (結束事件)

與流程相對應的方法是:

onStartShouldSetResponder(event) => true:在用戶開始進行觸摸操作時(手指剛剛接觸屏幕的瞬間),詢問是否申請成為觸摸事件的響應者,返回 true 為需要成為響應者。

onMoveShouldSetResponder(event) => true:如果綁定的View不是響應者,那麼會在用戶的觸摸點開始移動的時候再次詢問是否申請成為觸摸時間的響應者,返回true

為需要成為響應者。

假設組件通過上面的方法返回了 true,表示發出了申請需要成為響應者,但是我們前面說過,一個 RN 應用中只能有一個 responder,那麼接下來就需要協調所有組件的請求,看看這個響應者的位置給誰。

onResponderGrant:(event) => {}:View 申請成功,並成為了響應者。一般情況下,這時開始,組件進入了激活狀態,並進行一些事件處理或者手勢識別的初始化。

onResponderReject: (event) =>{}:View 申請失敗了,這就意味著有其他的組件正在成為或者已經成為了響應者,並且他不願意交出這個權利。所以你被拒絕了~

如果你成為了響應者,那麼會收到後續的事件輸入並由你來決定他的行為動作:

onResponderMove: (event) => 表示觸摸手指的移動事件,這個回調在一次完成的手勢動作中可能會非常頻繁的調用,所以這個回調函數裡面的內容需要盡量簡單

onResponderRelease: (event) => 表示觸摸完成,相當於前面講的 Touch 裡面的 onPressOut 方法,表示用戶已經完成了本次的觸摸操作,同時會釋放響應者這個權利。

在你成為響應者期間,其他組件也有可能會申請成為響應者,那麼此時RN會通過回調來詢問當前的響應者是否放權給其他申請者。回調如下:

onResponderTerminationRequest: (event) => true:如果我們返回的是 true,那就代表當前響應者同意放權,讓其他的組件來當響應者,自己回歸平淡的生活,同時也會回調一個函數,通知組件事件響應處理被終止了:

onResponderTerminate: (event) => {}:這個回調也會發生在系統直接終止組件的觸摸事件處理中,比如用戶在進行觸摸操作的時候,來電話了,或者意外閃退了。

相信大家都發現了,所有的方法都有一個 event 參數,裡面包含了一個觸摸事件數據 nativeEvent,nativeEvent 具體結構如下圖:

chanedTouches:event 數組,從上次回調上報的觸摸事件,到這次上報之間的所有事件數組。因為在用戶觸摸過程中會產生很多事件,有時候可能還沒來得及上報,系統就用這種方式批量上報

identifier:觸摸的 ID,這個 ID 存在周期為從觸摸開始到釋放為止,主要是用來區別在多點觸控的情況下,區分是哪個手指的觸摸事件。

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

pageX 和 pageY:觸摸點相對於屏幕的位置

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

timestamp:當前觸摸的事件的時間戳,可以用來進行滑動的相關計算(速度,停留時長)

touches:event 數組,多點觸摸的時候,包含當前所有觸摸點的事件

冒泡機制和事件捕獲

先前我們都是針對單一組件來說的,但是在實際開發過程中,我們往往會遇到很多嵌套之類的組件,那如果在我們多重嵌套的組件中,每層組件綁定了一個手勢響應且 onStartShouldSetResponder 或者 onMoveShouldSetResponder 回調都返回了 true 來申請成為響應者的話,又會怎麼樣呢?我們舉個栗子來看看:

在這個大栗子中,我們嵌套了兩層組件,使得組件布局如圖:

在 RN 中,默認情況下會遵循冒泡機制,也就是嵌套最深的組件最先開始響應,那麼我們栗子中的三層組件的 onStartShouldSetResponder 或者 onMoveShouldSetResponder 全部都返回 true 的情況下,那麼 C 組件會優先成為事件響應者。但在我們的實際開發中,可能你需要的是父組件去處理觸控事件,而禁止子組件響應,那腫么辦?。RN 給我們提供了一個事件捕獲機制,也就是在觸摸事件通過冒泡機制往下傳遞的時候,先詢問上層有申請的組件是否捕獲該事件,不給子組件傳遞事件,即上面的栗子中,正常情況下通過冒泡機制,我們的觸控事件會 A->B->C 這樣傳遞到 C 去響應事件,當 A 傳遞到 B 時,會詢問 A 是否捕獲這個觸控事件並且不再向下傳遞給 B 和 C,如果 A 確認捕獲,那麼 A 即成為這個事件的響應者。具體的回調是:

onStartShouldSetResponderCapture: () => true :在觸摸事件開始的時候,RN 容器的組件就會收到這麼一個回調函數,詢問是否捕獲事件成為響應者,如果返回true,表示確認捕獲事件

onMoveShouldSetResponderCapture: () =>true :在觸摸事件開始移動的時候,再次詢問是否捕獲事件成為響應者,如果返回 true,表示確認捕獲事件

PanResponder

除了 gesture responder system 之外,RN 還抽象出了一套 PanResponder 方法,這套方法的好處在於,使用起來更方便,在不改變原有的邏輯和流程的前提下,提供了更多的參數,包含了手勢進行過程中更多的信息,讓我們更好的去理解和處理用戶的手勢意圖,話不多說,直接上栗子。

在上面的栗子中,我們實現了在一個白色有邊框的事件響應者開始響應事件而變成綠色,然後實現拖拽效果並且在拖拽過程中變成紅色,最後在釋放手指又變回白色的這麼一個過程。

大體上和 gesture responder system 一樣,我們要注意的就是幾個方法的寫法加上了 Pan,並且幾個回調函數多了一個 gesture 參數,他具體長這樣的:

dx 和 dy:從觸摸操作開始到現在的累積橫向/縱向路程

moveX 和 moveY:最近一次移動時的屏幕橫/縱坐標

numberActiveTouches:當前在屏幕上的有效觸摸點的數量

stated:和之前一樣,用來識別手指的ID

vx 和 vy:當前橫向/縱向移動的速度

x0 和 y0:當觸摸操作開始時組件相對於屏幕的橫/縱坐標

總結

以上是我對 RN 的一些基礎學習和理解,只舉了一些簡單的栗子,要在項目里實現一些更為複雜的手勢操作,還需要進一步的摸索研究。另外需要注意的是,上述的回調函數都是在 JS 線程中進行的,可能會有些許延遲。

【海說軟體接受各種技術諮詢及開發業務】

-END-

推薦閱讀:

學生關於前端工作的幾個問題?
Facebook 首頁都使用了哪些技術提高訪問速度?
極樂技術周報(第二十二期)

TAG:前端开发 |