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 (
      
        this._gestureHandlers}
          style={[styles.rect,{
            backgroundColor: this.state.bg
          }]}>
          this._gestureHandlers2}
            style={[styles.rect2,{
              backgroundColor: this.state.bg2
            }]}
          >

          
        
      
    )
  }


}

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 的时候,但是同一个只能有一个事件处理响应者,这种情况怎么处理呢?为了便于描述,假设我们的组件布局如下:
react-native Touch事件的拦截与分发_第1张图片

在 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,表示需要成为响应者;

react-native Touch事件的拦截与分发_第2张图片

上如代码:
我们也是三层嵌套,只不过最外层是一个大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***方法。

你可能感兴趣的:(react)