目前手機(jī)市場(chǎng)上邀泉,全面屏?xí)r代已經(jīng)勢(shì)不可擋买窟,為了增大屏幕,一個(gè)個(gè)物理按鍵已漸漸消失在手機(jī)上贾铝。那么隙轻,手勢(shì)將成為在移動(dòng)應(yīng)用開發(fā)中一個(gè)重要的組成部分,移動(dòng)設(shè)備上手勢(shì)識(shí)別要比 web 端復(fù)雜得多垢揩,往往用戶的一個(gè)手勢(shì)玖绿,我們?cè)?APP 上要通過好幾個(gè)階段去判斷用戶的真實(shí)意圖是什么,在 ReactNative (以下簡(jiǎn)稱 RN)中針對(duì)手勢(shì)處理也提供了從最基本的點(diǎn)擊手勢(shì)到復(fù)雜的滑動(dòng)等一系列解決方案叁巨,讓我們一起去看看斑匪。
RN基本觸控組件
RN 的組件除了 Text,其他組件默認(rèn)是不支持點(diǎn)擊事件的锋勺,也不能成為一個(gè)觸摸事件的響應(yīng)者蚀瘸。RN 提供了幾個(gè)比較直接的處理響應(yīng)事件的組件,基本上能滿足大部分的點(diǎn)擊事件的處理需求庶橱。
TouchableHighlight
TouchableNativeFeedback (僅限 Android 平臺(tái))
TouchableOpacity
TouchableWithoutFeedback
這幾個(gè)組件的功能和使用方法基本類似纳击,只是就 Touch 的效果反饋上有所差異徽级,他們有如下幾個(gè)回調(diào)方法:
onPressIn:用戶觸摸開始的時(shí)候底扳,也就是手指剛落在 Touch 點(diǎn)擊區(qū)域內(nèi)的時(shí)觸發(fā)
onPressOut:用戶觸摸結(jié)束的時(shí)候顾复,也就是手指從 Touch 點(diǎn)擊區(qū)域內(nèi)抬起的時(shí)觸發(fā)
onPress:用戶完成一次從 onPressIn 到 onPressOut 的過程,且時(shí)間很短枫绅,即一次快速點(diǎn)擊操作時(shí)觸發(fā)
onLongPress:用戶觸發(fā) onPressIn 且手指一段時(shí)間內(nèi)沒有抬起時(shí)觸發(fā)
這里以 TouchableHighlight 為例泉孩,貼一個(gè) Touch 的基本用法:
RN 中提供的 Touch 組件的使用非常簡(jiǎn)單,可以參考官方文檔撑瞧,這里就不做詳細(xì)的介紹了棵譬,我們主要來說下用戶的觸摸事件處理。
gesture responder system
在 RN 中预伺,響應(yīng)手勢(shì)的基本單位是 responder订咸,具體點(diǎn)說就是最常見的 View 組件。任何的 View 組件都可以成為一個(gè)手勢(shì)的響應(yīng)者酬诀。其實(shí)要把一個(gè)普通的 View 組件開發(fā)成為一個(gè)能響應(yīng)手勢(shì)操作的 responder 很簡(jiǎn)單脏嚷,話不多說,我們舉栗子瞒御!
乍一看父叙,WillMount 里面的這幾個(gè)方法名字又長(zhǎng)又奇怪,但是等你了解了 RN 手勢(shì)響應(yīng)的流程了之后肴裙,記憶這幾個(gè)方法就非常簡(jiǎn)單了趾唱。在我們探索這幾個(gè)方法之前,我們首先要記住一個(gè)重要的點(diǎn):
一個(gè) RN 應(yīng)用中只能存在一個(gè) responder蜻懦!
?一次正常的手勢(shì)操作的流程如下所示:
是否響應(yīng) Touch 或者 move 手勢(shì)->grant(被激活) ->move->release (結(jié)束事件)
與流程相對(duì)應(yīng)的方法是:
onStartShouldSetResponder(event) => true:在用戶開始進(jìn)行觸摸操作時(shí)(手指剛剛接觸屏幕的瞬間),詢問是否申請(qǐng)成為觸摸事件的響應(yīng)者甜癞,返回 true 為需要成為響應(yīng)者。
onMoveShouldSetResponder(event) => true:如果綁定的View不是響應(yīng)者宛乃,那么會(huì)在用戶的觸摸點(diǎn)開始移動(dòng)的時(shí)候再次詢問是否申請(qǐng)成為觸摸時(shí)間的響應(yīng)者悠咱,返回true
為需要成為響應(yīng)者。
假設(shè)組件通過上面的方法返回了 true征炼,表示發(fā)出了申請(qǐng)需要成為響應(yīng)者析既,但是我們前面說過,一個(gè) RN 應(yīng)用中只能有一個(gè) responder谆奥,那么接下來就需要協(xié)調(diào)所有組件的請(qǐng)求眼坏,看看這個(gè)響應(yīng)者的位置給誰。
onResponderGrant:(event) => {}:View 申請(qǐng)成功酸些,并成為了響應(yīng)者宰译。一般情況下,這時(shí)開始擂仍,組件進(jìn)入了激活狀態(tài)囤屹,并進(jìn)行一些事件處理或者手勢(shì)識(shí)別的初始化。
onResponderReject: (event) =>{}:View 申請(qǐng)失敗了逢渔,這就意味著有其他的組件正在成為或者已經(jīng)成為了響應(yīng)者肋坚,并且他不愿意交出這個(gè)權(quán)利。所以你被拒絕了~
如果你成為了響應(yīng)者肃廓,那么會(huì)收到后續(xù)的事件輸入并由你來決定他的行為動(dòng)作:
onResponderMove: (event) => 表示觸摸手指的移動(dòng)事件智厌,這個(gè)回調(diào)在一次完成的手勢(shì)動(dòng)作中可能會(huì)非常頻繁的調(diào)用,所以這個(gè)回調(diào)函數(shù)里面的內(nèi)容需要盡量簡(jiǎn)單
onResponderRelease: (event) => 表示觸摸完成盲赊,相當(dāng)于前面講的 Touch 里面的 onPressOut 方法铣鹏,表示用戶已經(jīng)完成了本次的觸摸操作,同時(shí)會(huì)釋放響應(yīng)者這個(gè)權(quán)利哀蘑。
在你成為響應(yīng)者期間诚卸,其他組件也有可能會(huì)申請(qǐng)成為響應(yīng)者葵第,那么此時(shí)RN會(huì)通過回調(diào)來詢問當(dāng)前的響應(yīng)者是否放權(quán)給其他申請(qǐng)者『夏纾回調(diào)如下:
onResponderTerminationRequest: (event) => true:如果我們返回的是 true卒密,那就代表當(dāng)前響應(yīng)者同意放權(quán),讓其他的組件來當(dāng)響應(yīng)者棠赛,自己回歸平淡的生活哮奇,同時(shí)也會(huì)回調(diào)一個(gè)函數(shù),通知組件事件響應(yīng)處理被終止了:
onResponderTerminate: (event) => {}:這個(gè)回調(diào)也會(huì)發(fā)生在系統(tǒng)直接終止組件的觸摸事件處理中睛约,比如用戶在進(jìn)行觸摸操作的時(shí)候鼎俘,來電話了,或者意外閃退了辩涝。
相信大家都發(fā)現(xiàn)了贸伐,所有的方法都有一個(gè) event 參數(shù),里面包含了一個(gè)觸摸事件數(shù)據(jù) nativeEvent膀值,nativeEvent 具體結(jié)構(gòu)如下圖:
chanedTouches:event 數(shù)組棍丐,從上次回調(diào)上報(bào)的觸摸事件,到這次上報(bào)之間的所有事件數(shù)組沧踏。因?yàn)樵谟脩粲|摸過程中會(huì)產(chǎn)生很多事件歌逢,有時(shí)候可能還沒來得及上報(bào),系統(tǒng)就用這種方式批量上報(bào)
identifier:觸摸的 ID翘狱,這個(gè) ID 存在周期為從觸摸開始到釋放為止秘案,主要是用來區(qū)別在多點(diǎn)觸控的情況下,區(qū)分是哪個(gè)手指的觸摸事件潦匈。
locationX 和 locationY:觸摸點(diǎn)相對(duì)于組件的位置
pageX 和 pageY:觸摸點(diǎn)相對(duì)于屏幕的位置
target:接收當(dāng)前觸摸事件的組件 ID
timestamp:當(dāng)前觸摸的事件的時(shí)間戳阱高,可以用來進(jìn)行滑動(dòng)的相關(guān)計(jì)算(速度,停留時(shí)長(zhǎng))
touches:event 數(shù)組茬缩,多點(diǎn)觸摸的時(shí)候赤惊,包含當(dāng)前所有觸摸點(diǎn)的事件
冒泡機(jī)制和事件捕獲
先前我們都是針對(duì)單一組件來說的,但是在實(shí)際開發(fā)過程中凰锡,我們往往會(huì)遇到很多嵌套之類的組件未舟,那如果在我們多重嵌套的組件中,每層組件綁定了一個(gè)手勢(shì)響應(yīng)且 onStartShouldSetResponder 或者 onMoveShouldSetResponder 回調(diào)都返回了 true 來申請(qǐng)成為響應(yīng)者的話掂为,又會(huì)怎么樣呢裕膀?我們舉個(gè)栗子來看看:
在這個(gè)大栗子中,我們嵌套了兩層組件勇哗,使得組件布局如圖:
在RN中昼扛,默認(rèn)情況下會(huì)遵循冒泡機(jī)制,也就是嵌套最深的組件最先開始響應(yīng)欲诺,那么我們栗子中的三層組件的 onStartShouldSetResponder 或者 onMoveShouldSetResponder 全部都返回 true 的情況下抄谐,那么 C 組件會(huì)優(yōu)先成為事件響應(yīng)者渺鹦。但在我們的實(shí)際開發(fā)中,可能你需要的是父組件去處理觸控事件斯稳,而禁止子組件響應(yīng)海铆,那腫么辦迹恐?挣惰。RN 給我們提供了一個(gè)事件捕獲機(jī)制,也就是在觸摸事件通過冒泡機(jī)制往下傳遞的時(shí)候殴边,先詢問上層有申請(qǐng)的組件是否捕獲該事件憎茂,不給子組件傳遞事件,即上面的栗子中锤岸,正常情況下通過冒泡機(jī)制竖幔,我們的觸控事件會(huì) A->B->C 這樣傳遞到 C 去響應(yīng)事件,當(dāng) A 傳遞到 B 時(shí)是偷,會(huì)詢問 A 是否捕獲這個(gè)觸控事件并且不再向下傳遞給 B 和 C拳氢,如果 A確認(rèn)捕獲,那么 A 即成為這個(gè)事件的響應(yīng)者蛋铆。具體的回調(diào)是:
onStartShouldSetResponderCapture: () => true :在觸摸事件開始的時(shí)候馋评,RN 容器的組件就會(huì)收到這么一個(gè)回調(diào)函數(shù),詢問是否捕獲事件成為響應(yīng)者刺啦,如果返回true留特,表示確認(rèn)捕獲事件
onMoveShouldSetResponderCapture: () =>true :在觸摸事件開始移動(dòng)的時(shí)候,再次詢問是否捕獲事件成為響應(yīng)者玛瘸,如果返回 true蜕青,表示確認(rèn)捕獲事件
PanResponder
除了 gesture responder system 之外,RN 還抽象出了一套 PanResponder 方法糊渊,這套方法的好處在于右核,使用起來更方便,在不改變?cè)械倪壿嫼土鞒痰那疤嵯旅烊蓿峁┝烁嗟膮?shù)贺喝,包含了手勢(shì)進(jìn)行過程中更多的信息,讓我們更好的去理解和處理用戶的手勢(shì)意圖芒篷,話不多說搜变,直接上栗子。
在上面的栗子中针炉,我們實(shí)現(xiàn)了在一個(gè)白色有邊框的事件響應(yīng)者開始響應(yīng)事件而變成綠色挠他,然后實(shí)現(xiàn)拖拽效果并且在拖拽過程中變成紅色,最后在釋放手指又變回白色的這么一個(gè)過程篡帕。
大體上和 gesture responder system 一樣殖侵,我們要注意的就是幾個(gè)方法的寫法加上了 Pan,并且?guī)讉€(gè)回調(diào)函數(shù)多了一個(gè) gesture 參數(shù)贸呢,他具體長(zhǎng)這樣的:
dx 和 dy:從觸摸操作開始到現(xiàn)在的累積橫向/縱向路程
moveX 和 moveY:最近一次移動(dòng)時(shí)的屏幕橫/縱坐標(biāo)
numberActiveTouches:當(dāng)前在屏幕上的有效觸摸點(diǎn)的數(shù)量
stated:和之前一樣,用來識(shí)別手指的ID
vx 和 vy:當(dāng)前橫向/縱向移動(dòng)的速度
x0 和 y0:當(dāng)觸摸操作開始時(shí)組件相對(duì)于屏幕的橫/縱坐標(biāo)
總結(jié)
以上是我對(duì) RN 的一些基礎(chǔ)學(xué)習(xí)和理解拢军,只舉了一些簡(jiǎn)單的栗子楞陷,要在項(xiàng)目里實(shí)現(xiàn)一些更為復(fù)雜的手勢(shì)操作,還需要進(jìn)一步的摸索研究茉唉。另外需要注意的是固蛾,上述的回調(diào)函數(shù)都是在 JS 線程中進(jìn)行的,可能會(huì)有些許延遲度陆。
【海說軟件接受各種技術(shù)咨詢及開發(fā)業(yè)務(wù)】
-END-?