ReactNative多個搖桿操控實現
需求
- 在ReactNative中實現多個搖桿操控,即多個滑塊同時響應不同手指的觸控。
調研準備階段
使用 GestureResponderHandlers
僞代碼
... componentWillMount() { this._gesture = { // 在用戶開始觸摸的時候(手指剛剛接觸屏幕的瞬間),是否願意成爲響應者? onStartShouldSetResponder: (evt: GestureResponderEvent) => true, // 如果View不是響應者,那麼在每一個觸摸點開始移動(沒有停下也沒有離開屏幕)時再詢問一次:是否願意響應觸摸交互呢? onMoveShouldSetResponder: (evt: GestureResponderEvent)=> true, // 如果View返回true,並開始嘗試成爲響應者,那麼會觸發下列事件之一 // View現在要開始響應觸摸事件了。這也是需要做高亮的時候,使用戶知道他到底點到了哪裏。 onResponderGrant: (evt: GestureResponderEvent)=>{}, // 用戶正在屏幕上移動手指時(沒有停下也沒有離開屏幕)。 onResponderMove: (evt: GestureResponderEvent)=>{}, // 觸摸操作結束時觸發,比如"touchUp"(手指擡起離開屏幕)。 onResponderRelease: (evt: GestureResponderEvent)=>{} }; } ... render() { return ( <View style={styles.container} {...this._gesture}> ... </View> ) }
結果分析
1. 只有一個滑塊的時候,可以使用,座標可以通過 evt.nativeEvent 結構裏的屬性得到。 2. 當有多個滑塊的時候,只能同時響應一個。即按下第一個手指後,再按下第二個手指,無法識別和區分。 3. 由於無法同時作爲手勢響應者,故不滿足需求,此方案排除。
使用 PanResponder
僞代碼
... componentWillMount() { this._panResponder = PanResponder.create({ // 要求成爲響應者 onStartShouldSetPanResponder: (evt, gestureState) => true, onMoveShouldSetPanResponder: (evt, gestureState) => true, // 是否劫持觸摸事件,使其他控件無法響應 onStartShouldSetPanResponderCapture: (evt, gestureState) => true, onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, // 是否同意其他組件申請時釋放當前響應者 onPanResponderTerminationRequest: () => false, // 開始手勢操作 onPanResponderGrant: (evt, gestureState) => { // gestureState.{x,y} 現在會被設置爲0 }, // 手勢移動 onPanResponderMove: (evt, gestureState) => { // 最近一次的移動距離爲gestureState.move{X,Y} // 從成爲響應者開始時的累計手勢移動距離爲gestureState.d{x,y} }, // 用戶放開了所有的觸摸點 onPanResponderRelease: (evt, gestureState) => {} }); } ... render() { return ( <View style={styles.container} {...this._panResponder.panHandlers}> ... </View> ) }
結果分析
1. 只有一個滑塊的時候,可以使用,座標和位移可以通過 gestureState 結構裏的屬性得到。 2. 通過閱讀源碼可以發現,其實 PanResponder 實際是對 GestureResponderHandlers 的封裝實現。 它內部對座標和移動距離做了計算並賦值在了 gestureState 結構中,爲開發者減輕了計算負擔。 3. 由於無法同時作爲手勢響應者,故不滿足需求,此方案排除。
使用Touchable接口
僞代碼
... render() { return ( <View style={styles.container} onTouchStart={(evt: GestureResponderEvent) => {}} onTouchMove={(evt: GestureResponderEvent) => {}} onTouchEnd={(evt: GestureResponderEvent) => {}} onTouchCancel={(evt: GestureResponderEvent) => {}}> ... </View> ) } ...
結果分析
1. 對於 iOS 設備,滿足需求,可以實現多個滑塊同時響應並能識別區分。 參數evt的類型同 GestureResponderHandlers 中的 evt, 所以座標和位移可以通過 evt.nativeEvent 結構裏的屬性得到。 2. 對於 Android 設備,仍然無法滿足需求,對於多個滑塊可同時響應,但無法識別區分。 3. 此方案可以用於 iOS,但對於 Android 需要尋找其他方案。
最終解決方案
對於 iOS 設備,在調研準備階段中發現,使用RN提供的 Touchable 接口可以滿足需求。
對於 Android 設備,由於沒有合適的RN解決方案,暫定以原生橋接的方式實現。
Android 橋接實現思路:
- 橋接原生ViewGroup,重寫onTouchEvent函數,實現對MotionEvent的監聽。
- 將onTouchEvent的 event 結構裏的屬性(座標和位移)通過 DeviceEventEmitter 發送到RN層。
- RN層聲明橋接的ViewGroup,其引用形式同 RN-View。並監聽 DeviceEventEmitter 的回傳,在不同的onTouch事件中做出對應的計算和展示。
Demo已上傳至Github:react-native-gesture-joystick