捕捉全局手势(How to build the AppStore "BUY" button)

Capturing global gestures (How to build the AppStore "BUY" button)

我希望在我的应用程序中有类似 AppStore 的购买流程。

  1. 第一次触摸时,“购买”按钮发生变化,要求用户确认。
  2. 如果用户触摸屏幕上的其他任何地方,“购买”按钮会变回正常。常规触摸被拦截。

我不确定在 react-native 中执行 (2) 的最佳方法是什么。

我可以使用响应系统(即 PanResponder)拦截整个屏幕的触摸。但是触摸永远不会到达购买按钮(TouchableWithHighlight 控件)。

触摸事件包含被触摸元素的节点id,可以与想要的元素进行比较,但这很难,因为可触摸区域可能包含多个节点。

如何解决这个问题?

我目前已经确定了这种方法:

  1. 全局拦截组件可以设置为"block gestures except this view"。
  2. 计算该视图的屏幕坐标。
  3. 通过此屏幕矩形内的触摸。

这个效果不错。由于在拦截模式下滚动不需要工作,我们不必考虑屏幕上目标位置的变化。但是,动画可能会有问题。

var EventEmitter = require('events').EventEmitter
UIManager = require('NativeModules').UIManager;

var CaptureAll = React.createClass({
  _panResponder: {},
  _isBlocking: false,
  _allowedRect: null,

  componentWillMount: function() {
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
      onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
      onPanResponderGrant: this._handlePanResponderGrant,
      onPanResponderMove: this._handlePanResponderMove,
      onPanResponderRelease: this._handlePanResponderEnd,
      onPanResponderTerminate: this._handlePanResponderEnd,
    });

    this.eventEmitter = new EventEmitter();
  },

  render: function() {
    return (
      <View style={this.props.style} {...this._panResponder.panHandlers}>{this.props.children}</View>
    );
  },

  _handleStartShouldSetPanResponderCapture: function(e: Object, gestureState: Object): boolean {
    // If we are not blocking, do not intercept
    if (!this.isBlocking)
      return false;

    // Let the touch through if it is in the allowed rect
    if (this.allowedRect && e.touchHistory.indexOfSingleActiveTouch) {
      var touch = e.touchHistory.touchBank[e.touchHistory.indexOfSingleActiveTouch];
      var r = this.allowedRect;
      if (touch.currentPageX >= r.x && touch.currentPageX <= (r.x + r.w)
        && touch.currentPageY >= r.y && touch.currentPageY <= (r.y + r.h)
      )
        return false;
    }

    // Intercept and block this touch
    this.eventEmitter.emit('block', { panEvent: e });
    return true;
  },

  _handleMoveShouldSetPanResponderCapture: function(e: Object, gestureState: Object): boolean {
    return this._handleStartShouldSetPanResponderCapture(e, gestureState);
  },

  _handlePanResponderGrant: function(e: Object, gestureState: Object) {},
  _handlePanResponderMove: function(e: Object, gestureState: Object) {},
  _handlePanResponderEnd: function(e: Object, gestureState: Object) {},

  blockExceptRect: function(x, y, w, h) {
    this.isBlocking = true;
    this.allowedRect = {x, y, w, h}
  },

  blockExceptComponent: function(component) {
    // Do not wait for the callback to block
    this.isBlocking = true;
    this.allowedRect = null;

    let handle = React.findNodeHandle(component);
    UIManager.measure(
      handle,
      (x, y, w, h, px, py) => {
        this.blockExceptRect(px, py, w, h);
      });
  },

  unblock: function() {
    this.isBlocking = false;
    this.allowedRect = null;
  },

  addListener: function(callback) {
    this.eventEmitter.addListener('block', callback);
  }
});