ReactNative多個搖桿操控實現

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章