Search code examples
reactjsanimationreact-nativereact-native-gesture-handler

Why are two separate components sharing state in React Native?


I'm trying to implement multi-touch slider functionality (based on the example code provided by React Native Gesture Handler).

I'm finding that when I create two TapOrPan components, they work fine individually but the slider state is shared when touching both sliders at the same time.

Why this is happening, given that I'm using two separate components? What am I missing?

Expo link for debugging: https://snack.expo.io/aPLAoFWar

import React, { Component } from 'react';
import { Animated, Dimensions, StyleSheet, Text, View } from 'react-native';
import {
  PanGestureHandler,
  TapGestureHandler,
  ScrollView,
  State,
} from 'react-native-gesture-handler';

export function TapOrPan({width, radius}) {
  const tapRef = React.createRef();
  const panRef = React.createRef();

  const _id = parseInt(Math.random() * 100);
  const _touchX = new Animated.Value(width / 2 - radius);
  const _circleValue = new Animated.Value(-radius);
  const _translateX = Animated.add(_touchX, _circleValue);
  const _onPanGestureEvent = Animated.event(
    [{nativeEvent: {x: _touchX}}],
    {useNativeDriver: true}
  );

  const styles = StyleSheet.create({
    horizontalPan: {
      backgroundColor: '#777',
      height: 120,
      justifyContent: 'center',
      marginVertical: 10,
    },
    circle: {
      backgroundColor: '#fff',
      borderRadius: radius,
      height: radius * 2,
      width: radius * 2,
    },
    wrapper: {
      flex: 1,
    },
  });

  function _onTapHandlerStateChange({ nativeEvent }) {
    console.log(_id.toString() + ": " + JSON.stringify(nativeEvent));
    if (nativeEvent.oldState === State.ACTIVE) {
      // Once tap happened we set the position of the circle under the tapped spot
      _touchX.setValue(nativeEvent.x);
    }
  }

  return (
    <TapGestureHandler
      ref={tapRef}
      onHandlerStateChange={_onTapHandlerStateChange}
      >
      <Animated.View style={styles.wrapper}>
        <PanGestureHandler
          ref={panRef}
          activeOffsetX={[0, 0]}
          onGestureEvent={_onPanGestureEvent}
          >
          <Animated.View style={styles.horizontalPan}>
            <Animated.View
              style={[
                styles.circle,
                {transform: [{translateX: _translateX}]},
              ]}
            />
          </Animated.View>
        </PanGestureHandler>
      </Animated.View>
    </TapGestureHandler>
  );
}

export default function Example() {
  const windowWidth = Dimensions.get('window').width;

  return (
    <ScrollView>
      <TapOrPan width={windowWidth} radius={30} />
      <TapOrPan width={windowWidth} radius={30} />
    </ScrollView>
  );
}


Solution

  • If think that it will not work as you want. There is no two coordinates for two different fingers with different directions moving. There is one coordinates of some point, that is calculated from all of your finger's coordinates (different at different platforms). https://github.com/software-mansion/react-native-gesture-handler/blob/d3c8ff130e7d925dae7cec149098c5d042a00120/android/lib/src/main/java/com/swmansion/gesturehandler/PanGestureHandler.java#L44