react-native Touch事件的攔截與分發

單組件觸摸事件處理

在React Native中,響應手勢的基本單位是responder,具體來說,就是最常見的View組件。任何的View組件,都是潛在的responder,如果某個View組件沒有響應手勢操作,那是因爲它還沒有被“開發”。

將一個普通的View組件開發成爲一個能響應手勢操作的responder,非常簡單,只需要按照React Native的gesture responder system的規範,在props上設置幾個方法即可。具體如下:

View.props.onStartShouldSetResponder
View.props.onMoveShouldSetResponder
View.props.onResponderGrant
View.props.onResponderReject
View.props.onResponderMove
View.props.onResponderRelease
View.props.onResponderTerminationRequest
View.props.onResponderTerminate

乍看之下,這幾個方法名字又長有奇怪,但是當了解了React Native對手勢響應的流程之後,記憶這幾個方法也非常容易。

要理解React Native的手勢操作過程,首先要記住一點:
一個React Native應用中只能存在一個responder

正因爲如此,gesture responder system中才存在_reject和_terminate方法。React Native事件響應的基本步驟如下:

1.用戶通過觸摸或者滑動來“激活”某個responder,這個步驟由View.props.onStartShouldSetResponder以及View.props.onMoveShouldSetResponder這兩個方法負負責處理,如果返回值爲true,則表示這個View能夠響應觸摸或者滑動手勢被激活;

2.如果組件被激活,View.props.onResponderGrant方法被調用,一般來說,這個時候需要去改變組建的底色或者透明度,來表示組件已經被激活;

3.接下來,用戶開始滑動手指,此時View.props.onResponderMove方法被調用;

4.當用戶的手指離開屏幕之後,View.props.onResponderRelease方法被調用,此時組件恢復被觸摸之前的樣式,例如底色和透明度恢復之前的樣式,完成一次手勢操作;

綜上所述,一次正常的手勢操作的流程如下所示:
響應touch或者move手勢 -> grant(被激活) -> move -> release(結束事件)

來段簡單的示例代碼:

import React, { Component } from 'react';
import {
  Text,
  View,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  TouchableHighlight,
  TouchableWithoutFeedback
} from 'react-native';
import { Navigation } from 'react-native-navigation';
/**
 * 驗證父View與子View事件的分發與攔截(有圖有真相)
 */
class TouchViewScreen extends Component {
  static navigatorStyle = {
    drawUnderNavBar: false,
    tabBarHidden: true
  };

  constructor(props) {
    super(props);
    this.state={
      bg: 'white',
      bg2: 'white'
    }
  }

  componentWillMount(){
    this._gestureHandlers = {
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{this.setState({bg: 'red'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg: 'white'})},

      //----------------------外層View攔截了點擊事件------------------------
      // onStartShouldSetResponderCapture: () => true,
      // onMoveShouldSetResponderCapture: ()=> true,
    };
    this._gestureHandlers2 = {
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{this.setState({bg2: 'green'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg2: 'white'})}
    }
  }

  render() {
    return (
      <View style={styles.container}>
        <View
          {...this._gestureHandlers}
          style={[styles.rect,{
            backgroundColor: this.state.bg
          }]}>
          <View
            {...this._gestureHandlers2}
            style={[styles.rect2,{
              backgroundColor: this.state.bg2
            }]}
          >

          </View>
        </View>
      </View>
    )
  }


}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rect: {
    width: 200,
    height: 200,
    borderWidth: 1,
    borderColor: 'black',
    justifyContent: 'center',
    alignItems: 'center',
  },
  rect2: {
    width: 100,
    height: 100,
    borderWidth: 1,
    borderColor: 'black'
  }
});

export default TouchViewScreen;

嵌套組件事件處理

上一小節介紹的都是針對單個組件來說,事件處理的流程和機制。但是前面也提到了,當組件需要作爲事件處理響應者時,需要通過 onStartShouldSetResponder 或者 onMoveShouldSetResponder 回調返回值爲 true 來申請。假如當多個組件嵌套的時候,這兩個回調都返回了 true 的時候,但是同一個只能有一個事件處理響應者,這種情況怎麼處理呢?爲了便於描述,假設我們的組件佈局如下:
這裏寫圖片描述

在 RN 中,默認情況下使用冒泡機制,響應最深的組件最先開始響應,所以前面描述的這種情況,如圖中,如果 A、B、C 三個組件的 on*ShouldSetResponder 都返回爲 true,那麼只有 C 組件會得到響應成爲響應者。這種機制才能保證了界面所有的組件才能得到響應。但是有些情況下,可能父組件可能需要處理事件,而禁止子組件響應。RN 提供了一個劫持機制,也就是在觸摸事件往下傳遞的時候,先詢問父組件是否需要劫持,不給子組件傳遞事件,也就是如下兩個回調:

View.props.onStartShouldSetResponderCapture:這個屬性接收一個回調函數,函數原型是 function(evt): bool,在觸摸事件開始(touchDown)的時候,RN 容器組件會回調此函數,詢問組件是否要劫持事件響應者設置,自己接收事件處理,如果返回 true,表示需要劫持;

View.props.onStartShouldSetResponder,這個屬性接收一個回調函數,函數原型是 function(evt): bool,在觸摸事件開始(touchDown)的時候,RN 會回調此函數,詢問組件是否需要成爲事件響應者,接收事件處理,如果返回 true,表示需要成爲響應者;

這裏寫圖片描述

上如代碼:
我們也是三層嵌套,只不過最外層是一個大View。其實我們在觸摸最裏層的View,三個View都會有感知,但是爲什麼只會響應最裏層View的事件呢?

也就是說,當我們觸摸C組件的時候,先去問一下A組件是否要攔截,如果A不攔截,在去問一下B組件,如果B不攔截,再去問一下C是否攔截,最終三個都不攔截(默認都不攔截)。

/**
     * If a parent `View` wants to prevent a child `View` from becoming responder on a touch start,
     * it should have this handler which returns `true`.
     *
     * `View.props.onStartShouldSetResponderCapture: (event) => [true | false]`, where `event` is a
     * synthetic touch event as described above.
     */
    onStartShouldSetResponderCapture: PropTypes.func,

接下來,順序反過來,去問C是否想成爲事件響應者,恰巧C想成爲事件響應者(默認應該是true)。

/**
     * Does this view want to become responder on the start of a touch?
     *
     * `View.props.onStartShouldSetResponder: (event) => [true | false]`, where `event` is a
     * synthetic touch event as described above.
     */
    onStartShouldSetResponder: PropTypes.func,

現在,只是C這個組件需要成爲事件響應者,但是不一定能夠成功。只有在調用了onResponderGrant纔是真正成爲響應者,並且會去處理後面的事件。

如果在B裏面使用onStartShouldSetResponderCapture: () => true , 說明B想攔截事件,下面便會執行onResponder***方法。

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