Как сделать TouchableOpacity или TouchableHighlight интерактивными на устройствах, поддерживающих 3D Touch (принудительное касание)

У меня есть этот код:

<View {...this.panGesture.panHandlers}>
    <Animated.View>

        <View style={{ position: 'absolute' }}>
            <TouchableHighlight onPress={ () => {console.log('hello')}>
            </TouchableHighlight>
        </View>

    </Animated.View>
</View>

и я не могу из любви к себе заставить onPress работать на iPhone 6s и выше, когда включено 3D Touch.

Я перепробовал большинство решений, предложенных здесь, но безуспешно.

Буду признателен за любую помощь!


person SudoPlz    schedule 01.02.2017    source источник


Ответы (2)


Я смог обойти эту конкретную проблему, убедившись, что родительский PanResponder не захватывает отвечающего при движении, пока касание не переместится из исходной точки:

PanResponder.create({

  //... other responder callbacks

  onMoveShouldSetPanResponder(e, gestureState) {
    if (gestureState.dx === 0 || gestureState.dy === 0) {
      return false;
    }
    return true;
  }
});

Это было примерно в React Native 0,10 дня, с тех пор не пробовал. Надеюсь, поможет!

person jevakallio    schedule 01.02.2017
comment
Это, кажется, не решает мою проблему. Конечно, мне нужно протестировать устройство с 3d touch, но на устройствах без 3d touch я вижу, что onMoveShouldSetPanResponder вообще не вызывается, когда я нажимаю TouchableHighlight, поэтому нет смысла добавлять туда код..! Это вызывается только тогда, когда я перетаскиваю / перемещаю палец по экрану, а не одним нажатием. Любые идеи? - person SudoPlz; 01.02.2017
comment
Этот обходной путь устранил проблему, которая возникает только на 3D-устройствах. Я не могу гарантировать, что это сработает, но я думаю, вам стоит попробовать! - person jevakallio; 01.02.2017

Хорошо, после того, как я много возился с этим, я понял, что решение было частично тем, что сказал jevakallio, НО я больше не могу использовать Touchables, когда включено 3D Touch, поэтому мне нужно воспроизвести их поведение вручную.

Теперь мой код выглядит так: дочерний код:

let touchable;

if (View.forceTouchAvailable === false) { // if 3d touch is disabled
    // just use a Touchable and all should be fine.
    touchable = (<TouchableHighlight
        onPress={ () => {console.log('hello world');}
        style={this.props.style}
        underlayColor={this.props.highlightBgColor}
    >
        {this.props.children}
    </TouchableHighlight>);
} else { // Otherwise if 3D touch is enabled
    // we'll have to use a child view WITH a PanResponder
    touchable = (<View
      style={[this.props.style, {
        backgroundColor: this.state.panViewIsTapped ?
          this.props.highlightBgColor
          :
          bgColor,
      }]}
      {...this.panResponderChild.panHandlers}
    >
      {this.props.children}
    </View>);
}

return (<View {...this.panResponderParent.panHandlers}>
    <Animated.View>

        <View style={{ position: 'absolute' }}>
            {touchable}
        </View>

    </Animated.View>
</View>);

Код дочернего панответчика (this.panResponderChild):

this.panResponderChild = PanResponder.create({

        // true because we want tapping on this to set it as the responder
        onStartShouldSetPanResponder: () => true,

        // true because we want this to capture the responder lock from it's parent on start
        onStartShouldSetPanResponderCapture: () => true,

        // when the pan responder lock is terminated, set the pan view as NOT tapped
        onPanResponderTerminate: () => {
          this.setState({ panViewIsTapped: false });
        },

        // true so that the parent can grab our responder lock if he wan'ts to.
        onPanResponderTerminationRequest: () => true,

        // false because we DON'T want this btn capturing the resp lock from it's parent on move
        onMoveShouldSetPanResponderCapture: () => false,

        // false because we DON'T want moving the finger on this to set it as the responder
        onMoveShouldSetPanResponder: () => false,

        onPanResponderGrant: () => {
          this.setState({ panViewIsTapped: true });
        },

        onPanResponderRelease: () => {
          this.setState({ panViewIsTapped: false });
          console.log('hello world');
        },
})

Код родительского панорамного ответчика (this.panResponderParent):

this.panResponderParent = PanResponder.create({

      // true because we want tapping on the cal, to set it as a responder
      onStartShouldSetPanResponder: () => true,

      // false because we DON'T want to grab the responder lock from our children on start
      onStartShouldSetPanResponderCapture: () => false,

      /*
      onMoveShouldSetPanResponderCapture:
        false because we don't want to accidentally grab the responder lock from
        our children on movement.
          That's because sometimes even a small tap contains movement,
          and thus a big percentage of taps will not work.
          Keeping that flag false does not nessecarily mean that our children will
         always capture the responder lock on movement, (they can if they want),
         we're just not strict enough to grab it from them.
      */
      onMoveShouldSetPanResponderCapture: () => false,

      /*
      onMoveShouldSetPanResponder:
        We DO want moving the finter on the cal, to set it as a responder,
        BUT, we don't always want moving the finger on an appointment setting this parent
        as the responder.

        Logic:
        So, when the dx AND dy of the pan are 0, we return false, because we don't
        want to grab the responder from our appointment children.

        For anything other than that we just allow this parent to become the responder.

        (dx, dy: accumulated distance of the gesture since the touch started)
      */
      onMoveShouldSetPanResponder: (e, gestureState) =>
        !(gestureState.dx === 0 || gestureState.dy === 0),


    });
person SudoPlz    schedule 06.02.2017