來源:Songlcy 發(fā)布時間:2018-11-01 14:37:42 閱讀量:1799
源碼已開源到Github,詳細代碼可以查看:《React Native 觸摸事件代碼實踐》。
在基礎(chǔ)篇,對RN中的觸摸事件做了詳細的介紹。相信大家對于觸摸事件流程機制有了更為清晰的認識。沒有瀏覽的可以先看看基礎(chǔ)篇:《 React Native 手勢觸摸事件機制詳解(基礎(chǔ)篇)》
本篇博客中,同樣延續(xù)基礎(chǔ)篇中結(jié)尾的內(nèi)容,對觸摸事件的執(zhí)行流程從代碼層執(zhí)行流程進行更深的說明,并使用RN系統(tǒng)提供的高級API作為實戰(zhàn),完成高仿微信通訊錄字母索引導(dǎo)航欄的效果。Gif圖加載較慢,來看下靜態(tài)圖效果:
一、RN系統(tǒng)觸摸組件觸發(fā)機制
在基礎(chǔ)篇中,我們對RN系統(tǒng)提供的觸摸組件(Touchable*)作了一些介紹,并通過代碼來說明如何實現(xiàn)點擊相關(guān)的事件,如下:
<TouchableOpacity
style={ styles.btn }
onPressIn={(evt)=>this.onPressIn(evt)}
onPressOut={(evt)=>this.onPressOut(evt)}
onLongPress={(evt)=>this.onLongPress(evt)}
onPress={(evt)=>this.onPress(evt)}>
<Text style={ styles.btnText }>點擊按鈕</Text>
</TouchableOpacity>
事件的實現(xiàn)方式很簡單,使用 console.log 打印當(dāng)前的事件名稱。
測試用例
(1)手指按下,不彈起
(2)手指按下,彈起
(3)手指按下,不彈起,并滑動出View范圍
測試結(jié)果
(1)手指按下,不彈起
未綁定長按事件
onPressIn
綁定長按事件
onPressIn
onLongPress
(2)手指按下,彈起
非長按(單擊)
onPressIn
onPressOut
onPress
長按
onPressIn
onLongPress
onPressOut
(3)手指按下,不彈起,并滑動出View范圍
非長按
onPressIn
onPressOut
長按
onPressIn
onLongPress
onPressOut
結(jié)論
從上述三種觸發(fā)方式中打印的log,我們可以對 onPress,onPressIn,onPressOut,onLongPress 事件的觸發(fā)條件和觸發(fā)順序有一個比較清晰的了解:
(1)未綁定長按事件,手指按下,不彈起只會觸發(fā) onPressIn,反之,則會觸發(fā) onPressIn -> onLongPress
(2)未綁定長按事件,手指按下,彈起,即單擊,會觸發(fā) onPressIn -> onPressOut -> onPress,反之,如果長按后彈起,會觸發(fā)onPressIn -> onLongPress -> onPressOut
(3)未綁定長按事件,手指按下,不彈起,并滑動出View范圍,組件會自動失去焦點,并會觸發(fā) onPressIn -> onPressOut,反之,則會觸發(fā) onPressIn -> onLongPress -> onPressOut
可以看到,系統(tǒng)為我們提供了觸摸組件相對來說較為簡單,基本可以完全覆蓋點擊場景。如果涉及較為復(fù)雜的觸摸交互,還是需要自定義觸摸事件上場,接下來我們繼續(xù)來看自定義觸摸事件的觸發(fā)機制。
二、RN系統(tǒng)觸摸組件觸發(fā)機制
單組件
在RN中任何View組件都可以實現(xiàn)自定義觸摸事件,方式很簡單,只需要將觸摸事件行為方式作為props屬性傳遞給View組件即可。測試代碼如下:
componentWillMount() {
this.gestureHandlers = {
/**
* 在手指觸摸開始時申請成為響應(yīng)者
*/
onStartShouldSetResponder: (evt) => {
console.log('onStartShouldSetResponder');
return true;
},
/**
* 在手指在屏幕移動時申請成為響應(yīng)者
*/
onMoveShouldSetResponder: (evt) => {
console.log('onMoveShouldSetResponder');
return true;
},
/**
* 申請成功,組件成為了事件處理響應(yīng)者,這時組件就開始接收后序的觸摸事件輸入。
* 一般情況下,這時開始,組件進入了激活狀態(tài),并進行一些事件處理或者手勢識別的初始化
*/
onResponderGrant: (evt) => {
console.log('onResponderGrant');
},
/**
* 表示申請失敗了,這意味者其他組件正在進行事件處理,
* 并且它不想放棄事件處理,所以你的申請被拒絕了,后續(xù)輸入事件不會傳遞給本組件進行處理。
*/
onResponderReject: (evt) => {
console.log('onResponderReject');
},
/**
* 表示手指按下時,成功申請為事件響應(yīng)者的回調(diào)
*/
onResponderStart: (evt) => {
console.log('onResponderStart');
},
/**
* 表示觸摸手指移動的事件,這個回調(diào)可能非常頻繁,所以這個回調(diào)函數(shù)的內(nèi)容需要盡量簡單
*/
onResponderMove: (evt) => {
console.log('onResponderMove');
},
/**
* 表示觸摸完成(touchUp)的時候的回調(diào),表示用戶完成了本次的觸摸交互,這里應(yīng)該完成手勢識別的處理,
* 這以后,組件不再是事件響應(yīng)者,組件取消激活
*/
onResponderRelease: (evt) => {
console.log('onResponderRelease');
},
/**
* 組件結(jié)束事件響應(yīng)的回調(diào)
*/
onResponderEnd: (evt) => {
console.log('onResponderEnd');
},
/**
* 當(dāng)其他組件申請成為響應(yīng)者時,詢問你是否可以釋放響應(yīng)者角色讓給其他組件
*/
onResponderTerminationRequest: (evt) => {
console.log('onResponderTerminationRequest');
return true;
},
/**
* 如果 onResponderTerminationRequest 回調(diào)函數(shù)返回為 true,
* 則表示同意釋放響應(yīng)者角色,同時會回調(diào)如下函數(shù),通知組件事件響應(yīng)處理被終止
* 這可能是由于其他View通過onResponderTerminationRequest請求的,也可能是由操作系統(tǒng)強制奪權(quán)(比如iOS上的控制中心或是通知中心)。
*/
onResponderTerminate: (evt) => {
console.log('onResponderTerminate');
}
}
}
render() {
return (
<View { ...this.gestureHandlers } style={ styles.container } />
)
}
上述代碼中我們在 ComponentWillMount 生命周期函數(shù)中定義了 gestureHandlers,并定義了觸摸事件行為函數(shù),通過 log 打印的方式來看自定義觸摸事件執(zhí)行機制。View組件的 style 樣式為一個藍色填充的圓形,附上效果圖
效果圖
測試用例
(1)onStartShouldSetResponder 方法返回 false,onMoveShouldSetResponder 返回 false
(2)onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 false
(3)onStartShouldSetResponder 方法返回 false,onMoveShouldSetResponder 返回 true
(4)onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 true
測試觸摸方式分兩種:
(1)單擊事件
(2)滑動事件
測試結(jié)果
(1)onStartShouldSetResponder 方法返回 false,onMoveShouldSetResponder 返回 false
單擊
onStartShouldSetResponder
滑動
onStartShouldSetResponder
onMoveShouldSetResponder
(2)onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 false
單擊
按下
onStartShouldSetResponder
onResponderGrant
onResponderStart
彈起
onResponderEnd
onResponderRelease
滑動
按下
onStartShouldSetResponder
onResponderGrant
onResponderStart
移動
onResponderMove(多次執(zhí)行)
彈起
onResponderEnd
onResponderRelease
(3)onStartShouldSetResponder 方法返回 false,onMoveShouldSetResponder 返回 true
單擊
onStartShouldSetResponder
滑動
按下
onStartShouldSetResponder
移動
onMoveShouldSetResponder
onResponderGrant
onResponderMove(多次執(zhí)行)
彈起
onResponderEnd
onResponderRelease
(4)onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 true
與 onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 false 執(zhí)行結(jié)果完全一致。
測試結(jié)論
(1)當(dāng) onStartShouldSetResponder 、onMoveShouldSetResponder 方法都返回 false,當(dāng)手指按下或者進行移動時,RN系統(tǒng)詢問當(dāng)前組件并發(fā)現(xiàn)在點擊和滑動時都不申請成為事件響應(yīng)者,只會執(zhí)行 onStartShouldSetResponder 、onMoveShouldSetResponder 方法,并立刻結(jié)束,后續(xù)觸摸事件不會被觸發(fā)。
(2)onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 false,當(dāng)手指按下時,RN系統(tǒng)詢問當(dāng)前組件,發(fā)現(xiàn) onStartShouldSetResponder 方法返回 true,此時申請成為響應(yīng)者(不存在多組件觸摸事件互斥場景),在申請成功后,會依次執(zhí)行 onResponderGrant 、onResponderStart 方法。由于當(dāng)前組件已經(jīng)成為了響應(yīng)者,此時手指如果繼續(xù)移動,系統(tǒng)并不會觸發(fā) onMoveShouldSetResponder 方法,并立刻執(zhí)行 onResponderMove 方法。當(dāng)手指離開屏幕,此時會觸發(fā)onResponderEnd、onResponderRelease 方法,一次完整的觸摸事件結(jié)束。
(3)onStartShouldSetResponder 方法返回 false,onMoveShouldSetResponder 返回 true,當(dāng)手指按下時,RN系統(tǒng)詢問當(dāng)前組件,發(fā)現(xiàn) onStartShouldSetResponder 方法返回 false,此時不申請成為響應(yīng)者,執(zhí)行 onStartShouldSetResponder方法完畢后立刻結(jié)束。由于當(dāng)前組件不是響應(yīng)者,此時手指按下后如果繼續(xù)移動,系統(tǒng)會觸發(fā) onMoveShouldSetResponder 方法,再次詢問當(dāng)前組件是否要申請成為響應(yīng)者,此方法返回 true ,即申請成為響應(yīng)者,在申請成功后,會依次執(zhí)行 onResponderGrant 、onResponderMove 方法。當(dāng)手指離開屏幕,此時會觸發(fā)onResponderEnd、onResponderRelease 方法,一次完整的觸摸事件結(jié)束。
??與(2)測試對比可以發(fā)現(xiàn):當(dāng) onStartShouldSetResponder 方法返回 true,法返手指按下申請響應(yīng)者授權(quán)成功后,會依次執(zhí)行 onResponderGrant 、onResponderStart 方法。而當(dāng) onStartShouldSetResponder 方法返回 false,onMoveShouldSetResponder 方法返回 true 時,此時在滑動申請成為響應(yīng)者授權(quán)成功后,會依次執(zhí)行 onResponderGrant 、onResponderMove 方法,而不會觸發(fā)onResponderStart 方法。
(4)onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 true,結(jié)論是與onStartShouldSetResponder 方法返回 true,onMoveShouldSetResponder 返回 false,執(zhí)行機制相同。其實此時結(jié)合上述的分析,我們很容易明白原因。在onStartShouldSetResponder 方法返回 true,并申請成為響應(yīng)者,此時在移動過程中系統(tǒng)不會再去詢問 onMoveShouldSetResponder,即會忽略該方法。并繼續(xù)執(zhí)行后續(xù)的觸摸事件。最終形成一次完整的觸摸事件操作。
多組件
在實際開發(fā)過程中,肯定不僅僅處理單組件觸摸事件行為,可能會涉及到多個組件間的交互,或者多層次的嵌套組件交互。在RN中,我們知道在同一時刻,只會存在一個響應(yīng)者。當(dāng)使用一個手指激活一個組件成為響應(yīng)者后,在當(dāng)前手指不釋放的情況下,又去觸摸激活另一個組件會發(fā)生什么呢?這就會涉及到多個組件間共同申請響應(yīng)者互斥的場景。還記得我們在基礎(chǔ)篇中介紹的onResponderTerminationRequest、onResponderTerminate 方法嗎?
onResponderTerminationRequest 方法需要返回一個bool類型值,來決定當(dāng)有其他組件同時申請成響應(yīng)者時,當(dāng)前組件是否放棄響應(yīng)者權(quán)限。此時會分為兩種情況:
(1)onResponderTerminationRequest 方法返回 true
此時系統(tǒng)發(fā)現(xiàn)當(dāng)有新當(dāng)組件申請成為響應(yīng)者時,當(dāng)前組件會釋放掉響應(yīng)者身份,并將權(quán)限交與新組件。新組件onResponderGrant 方法被調(diào)用 ,當(dāng)前組件的 onResponderTerminate 方法被調(diào)用,并將響應(yīng)者身份權(quán)限狀態(tài)釋放。
(2)onResponderTerminationRequest 方法返回 false
此時系統(tǒng)發(fā)現(xiàn)當(dāng)有新當(dāng)組件申請成為響應(yīng)者時,當(dāng)前組件不會釋放掉響應(yīng)者身份。新組件申請響應(yīng)者權(quán)限失敗,這也意味其他組件正在進行事件處理,并且它不想放棄事件處理,所以你的申請被拒絕了,后續(xù)輸入事件不會傳遞給本組件進行處理。新組件的 onResponderReject 方法會被調(diào)用。
由于在模擬器上不好模擬多指觸控請求,當(dāng)家可以嘗試編寫demo,并運行真機查看效果。
在基礎(chǔ)篇中,我們同樣介紹了事件攔截機制。通過 onStartShouldSetResponderCapture、onMoveShouldSetResponderCapture 方法來決定當(dāng)前組件是否需要攔截觸摸事件。當(dāng)組件的 onStartShouldSetResponderCapture 或者 onMoveShouldSetResponderCapture 方法 返回 true,表示會攔截掉當(dāng)前觸摸事件,交給自己處理。此時系統(tǒng)會忽略不執(zhí)行 onStartShouldSetResponder、onMoveShouldSetResponder 方法直接執(zhí)行onResponderGrant 標(biāo)示當(dāng)前組件已成為事件響應(yīng)者,進而處理后續(xù)觸摸事件。當(dāng)前組件如果有子組件,那么子組件不會收到觸摸事件。
三、觸摸手勢高級API - PanResponder
通過上述對于自定義View的觸摸事件機制來看,對于開發(fā)者來說還是較為復(fù)雜,不夠友好。使用起來也不是特別方便。官方同樣考慮到這個問題,為我們提供了更高抽象到API:PanResponder。
源碼中對于create的描述:
/**
* @param config Enhanced versions of all of the responder callbacks
* that provide not only the typical `ResponderSyntheticEvent`, but also the
* `PanResponder` gesture state. Simply replace the word `Responder` with
* `PanResponder` in each of the typical `onResponder*` callbacks. For
* example, the `config` object would look like:
*
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onPanResponderReject: (e, gestureState) => {...}`
* - `onPanResponderGrant: (e, gestureState) => {...}`
* - `onPanResponderStart: (e, gestureState) => {...}`
* - `onPanResponderEnd: (e, gestureState) => {...}`
* - `onPanResponderRelease: (e, gestureState) => {...}`
* - `onPanResponderMove: (e, gestureState) => {...}`
* - `onPanResponderTerminate: (e, gestureState) => {...}`
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
*
* In general, for events that have capture equivalents, we update the
* gestureState once in the capture phase and can use it in the bubble phase
* as well.
*
* Be careful with onStartShould* callbacks. They only reflect updated
* `gestureState` for start/end events that bubble/capture to the Node.
* Once the node is the responder, you can rely on every start/end event
* being processed by the gesture and `gestureState` being updated
* accordingly. (numberActiveTouches) may not be totally accurate unless you
* are the responder.
*/
create(config: PanResponderCallbacks): PanResponderInstance;
PanResponder 的使用方式非常簡單,通過調(diào)用 PanResponder 的 create 靜態(tài)方法創(chuàng)建手勢對象,傳入 config 參數(shù)實現(xiàn)對應(yīng)的觸摸回調(diào)即可。手勢的邏輯和流程與自定義觸摸事件保持一致。最后調(diào)用 panResponer 的 panHandlers 作為 props 傳給View組件:
<View {...this.panResponder.panHandlers } style={ styles.container } />
官方對于 config 參數(shù)的描述:
@param config 所有響應(yīng)者回調(diào)的增強版本,不僅提供典型的 ResponderSyntheticEvent,而且還提供 PanResponder 手勢狀態(tài)。只需將 onResponder * 一詞改為 PanResponder 。例如,config 對象看起來像:
onMoveShouldSetPanResponder :( e,gestureState)=> {...}
onMoveShouldSetPanResponderCapture:(e,gestureState)=> {...}
onStartShouldSetPanResponder :( e,gestureState)=> {...}
onStartShouldSetPanResponderCapture:(e,gestureState)=> {...}
onPanResponderReject :( e,gestureState)=> {...}
onPanResponderGrant :( e,gestureState)=> {...}
onPanResponderStart :( e,gestureState)=> {...}
onPanResponderEnd :( e,gestureState)=> {...}
onPanResponderRelease :( e,gestureState)=> {...}
onPanResponderMove :( e,gestureState)=> {...}
onPanResponderTerminate:(e,gestureState)=> {...}
onPanResponderTerminationRequest:(e,gestureState)=> {...}
onShouldBlockNativeResponder :( e,gestureState)=> {...}
通常來說,對那些有對應(yīng)捕獲事件的事件來說,我們在捕獲階段更新 gestureState 一次,然后在冒泡階段直接使用即可。
注意 onStartShould* 回調(diào)。他們只會在此節(jié)點冒泡/捕獲的開始/結(jié)束事件中提供已經(jīng)更新過的 gestureState。一旦這個節(jié)點成為了事件的響應(yīng)者,則所有的開始/結(jié)束事件都會被手勢正確處理,并且 gestureState 也會被正確更新。(numberActiveTouches)有可能沒有包含所有的觸摸點,除非組件本身就是觸摸事件的響應(yīng)者。
可以發(fā)現(xiàn)PanResponder包含一個onShouldBlockNativeResponder 方法,該方法需要返回一個 bool 值,決定當(dāng)前組件是否應(yīng)該阻止原生組件成為JS響應(yīng)者。 默認返回true。目前只支持android平臺設(shè)備。
了解完P(guān)anResponder具有的觸摸方式,接下來我們看下gestureState參數(shù)包含的一些觸摸事件屬性:
stateID - 觸摸狀態(tài)的 ID。在屏幕上有至少一個觸摸點的情況下,這個 ID 會一直有效。
moveX - 最近一次移動時的屏幕橫坐標(biāo)
moveY - 最近一次移動時的屏幕縱坐標(biāo)
x0 - 當(dāng)響應(yīng)器產(chǎn)生時的屏幕橫坐標(biāo)
y0 - 當(dāng)響應(yīng)器產(chǎn)生時的屏幕縱坐標(biāo)
dx - 從觸摸操作開始時的累計橫向路程
dy - 從觸摸操作開始時的累計縱向路程
vx - 當(dāng)前的橫向移動速度
vy - 當(dāng)前的縱向移動速度
numberActiveTouches - 當(dāng)前在屏幕上的有效觸摸點的數(shù)量
從屬性的行為上來看,相對自定義的onResponder*觸摸事件,提供了更高級的描述。例如:移動速度、累計滑動的坐標(biāo)點等等。更方便開發(fā)者來處理一些復(fù)雜的觸摸交互。
四、觸摸手勢高級屬性 - pointerEvents
系統(tǒng)在View中提供了一個觸摸事件的高級屬性:pointerEvents。官方對該屬性的描述為:控制View是否可以成為觸摸事件的目標(biāo)。
官方為 pointerEvents 屬性提供了4個可供選擇的值:
none: View永遠不是觸摸事件的目標(biāo),即發(fā)生在本組件與本組件的子組件上的觸摸事件都會交給本組件的父組件處理.
box-none: 發(fā)生在本組件顯示范圍內(nèi),但不是子組件顯示范圍內(nèi)的事件交給本組件,在子組件顯示范圍內(nèi)交給子組件處理,即視圖永遠不是觸摸事件的目標(biāo),但它的子視圖可以是。
box-only: 發(fā)生在本組件顯示范圍內(nèi)的觸摸事件將全部由本組件處理,即使觸摸事件發(fā)生在本組件的子組件顯示范圍內(nèi)
auto: 視圖可以成為觸摸事件的目標(biāo),但視組件的不同而定,并不是所有的子組件都支持box-none和box-only兩個值
由于 pointerEvents 不會影響布局/外觀,因此我們選擇不在樣式上包含 pointerEvents。 是否將 pointerEvents 作為屬性來設(shè)置,視平臺而定。例如,在RN中,pointerEvents 作為View的屬性來設(shè)置。但在 CSS 中,可能以className來設(shè)置。
該屬性可以方便我們處理類似“穿透”事件傳遞的處理。例如,絕對定位導(dǎo)致多層次view間的遮蓋后,事件如何傳遞處理等。我們拿高德地圖為例:
在地圖組件上覆蓋了一層組件用來進行其他操作,又不想讓這個圖像組件影響用戶手指拖動地圖的操作,這時就需 pointerEvents 屬性來解決這個問題.
看上面的描述可能不太直觀,為了模仿上述效果,我們在代碼中定義一個可滑動列表,并且在列表上層定義一個View組件。
以上就是關(guān)于手勢觸摸的全部內(nèi)容了,我們花了大量的篇幅通過代碼執(zhí)行的日志結(jié)果分析了觸摸事件的行為方式,相信大家對于RN觸摸事件有了更加深刻的認知。學(xué)而不思則罔,思而不學(xué)則殆。接下來我們通過實現(xiàn)高仿微信通訊錄快速查找,來加深觸摸手勢在實際開發(fā)過程中的實現(xiàn)流程。
五、高仿微信通訊錄字母索引導(dǎo)航欄
實現(xiàn)微信通訊錄右側(cè)字母索引導(dǎo)航,需要我們處理手勢相關(guān)的操作行為。使用自定義觸摸事件及 PanResponder,都可以相對輕松的完成。本例中,我們將介紹使用 PanResponder 如何實現(xiàn)。
從整體交互分析來看,要實現(xiàn)該功能有以下幾點:
(1)手指按下或單擊時,字母導(dǎo)航欄背景色改變,得到當(dāng)前觸摸區(qū)域?qū)?yīng)的字母,并在屏幕中心以 Dialog 方式顯示,根據(jù)index 將 SectionLst 滑動到對應(yīng)位置
(2)手指觸摸字母導(dǎo)航欄區(qū)域,并滑動,則同樣得到當(dāng)前觸摸區(qū)域?qū)?yīng)的字母,并在屏幕中心以 Dialog 方式顯示,根據(jù)index 將 SectionLst 滑動到對應(yīng)位置
(3)手指離開屏幕,字母導(dǎo)航欄背景色還原,Dialog 隱藏
在實現(xiàn)之前,我們先定義好需要使用到的常量:
const { height } = Dimensions.get('window');
// 標(biāo)題欄高度
const HEADER_HEIGHT = 64;
// 字母搜索容器高度
const WORD_SEARCH_CONTAINER_HEIGHT = height - HEADER_HEIGHT;
// 字母搜索容器縱向 padding
const WORD_SEARCH_CONTAINER_V_PADDING = 8;
// 字母觸摸區(qū)域之外的高度(標(biāo)題欄,padding,margin等)
const WORD_TOUCH_OUTSIDE_HEIGHT = HEADER_HEIGHT + WORD_SEARCH_CONTAINER_V_PADDING * 2
// 單個字母高度
const WORD_HEIGHT = Math.floor((WORD_SEARCH_CONTAINER_HEIGHT - WORD_SEARCH_CONTAINER_V_PADDING * 2) / 28);
// 搜索字母,長度: 28
const WORDS = ['↑', '?' ,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W', 'X', 'Y', 'Z', '#'];
要實現(xiàn)第一步,首先需要使當(dāng)前索引導(dǎo)航欄成為事件響應(yīng)者。為了方便,我們將其封裝成單獨組件,也符合了RN的組件化開發(fā)思想。在手指按下時,需要使當(dāng)前組件申請成為響應(yīng)者,所以我們需要實現(xiàn) onStartSetShouldPanResponder 方法,并 return true。在成為響應(yīng)者,授權(quán)成功后,改變導(dǎo)航欄背景色,即在 onPanResponderGrant 方法中,更新當(dāng)前組件背景色。同時獲取當(dāng)前觸摸的y坐標(biāo)點,因為我們根據(jù)屏幕高度固定了每個字母占據(jù)的空間高度,所以可以根據(jù)當(dāng)前觸摸屏幕的y坐標(biāo)點結(jié)合字母空間高度計算出當(dāng)前的字母 index。根據(jù) index 即可獲取到第幾個字母。并作dialog展示。
onStartShouldSetPanResponderCapture: (evt, gestureState) => {
return true;
},
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
},
onStartShouldSetPanResponder: (evt, gestureState) => {
return true;
},
onPanResponderGrant: (evt, gestureState) => {
const { index, word } = this.getWord(gestureState.y0);
this.props.showWordDialog && this.props.showWordDialog(index, word);
this.changeBgColor(this.refs.wordContainer, 'rgba(0,0,0,0.3)');
}
第二步:監(jiān)聽手勢滑動,我們需要實現(xiàn) onPanResponderMove 方法,在該方法中,獲取當(dāng)前滑動所在屏幕的 y 坐標(biāo)點,重復(fù)第二步的計算,得出當(dāng)前觸摸區(qū)域?qū)?yīng)的字母。
onPanResponderMove:(evt, gestureState) => {
// 獲取當(dāng)前滑動到的位置,根據(jù)當(dāng)前位置,取出index 對應(yīng) word
const { index, word } = this.getWord(gestureState.moveY);
this.props.showWordDialog && this.props.showWordDialog(index, word);
}
第三步:在手指離開屏幕后,當(dāng)前觸摸事件結(jié)束,可以在 onPanResponderRelease 方法中以回調(diào)方式隱藏 dialog。
onPanResponderRelease:(evt, gestureState) => {
// 隱藏dialog
this.props.hideWordDialog && this.props.hideWordDialog();
this.changeBgColor(this.refs.wordContainer, 'transparent');
}
通過以上三步,我們就能很輕松的實現(xiàn)字母索引導(dǎo)航欄的效果了。為了高度可定制化,將dialog的實現(xiàn)方式剝離,以回調(diào)的方式在索引組件中實現(xiàn),降低組件間依賴。
/**
* 顯示字母Dialog提示
*/
showWordDialog(index, word) {
this.setState({
word
});
this.scrollSectionList(index)
}
/**
* 隱藏字母Dialog提示
*/
hideWordDialog() {
this.setState({ word: null });
}
/**
* 滾動SectionList
*/
scrollSectionList(index) {
this.refs.sectionList.scrollToLocation({animated: false, itemIndex: 0, sectionIndex: index, viewOffset: 26});
}
根據(jù)索引滑動列表到功能,我們可以結(jié)合SectionList來實現(xiàn)。具體可以看源碼即可。
關(guān)于React Native觸摸手勢事件的內(nèi)容就全部結(jié)束了。通過基礎(chǔ)篇、進階篇兩篇內(nèi)容從基礎(chǔ)的了解到功能的實踐,相信大家對RN的觸摸事件系統(tǒng)有了非常深刻的理解。大家有任何問題,可以加入我們的技術(shù)討論組交流溝通。我的微信號:mir_song
---------------------
作者:Songlcy
來源:CSDN
原文:https://blog.csdn.net/u013718120/article/details/83213261
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!