#atom

Advanced touch interaction management for React Native applications

Core Idea: React Native Gesture Handler provides a more direct and customizable way to handle touch interactions by connecting to the native gesture recognition system, enabling complex gesture patterns and improved performance compared to standard React Native touch handlers.

Key Elements

Core Concepts

Main Gesture Types

Advantages Over Standard RN

Implementation Techniques

Basic Setup

// Installation
npm install react-native-gesture-handler

// In App.js or entry file
import 'react-native-gesture-handler';

Simple Tap Handler

import { TapGestureHandler, State } from 'react-native-gesture-handler';
import { View, Text } from 'react-native';

function TapExample() {
  const onHandlerStateChange = ({ nativeEvent }) => {
    if (nativeEvent.state === State.ACTIVE) {
      console.log('Tap detected');
    }
  };

  return (
    <TapGestureHandler onHandlerStateChange={onHandlerStateChange}>
      <View style={{ padding: 20, backgroundColor: 'blue' }}>
        <Text style={{ color: 'white' }}>Tap me</Text>
      </View>
    </TapGestureHandler>
  );
}

Drag Gesture with Animation

import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, { 
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue
} from 'react-native-reanimated';

function DraggableBox() {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);

  const panGestureEvent = useAnimatedGestureHandler({
    onStart: (_, ctx) => {
      ctx.startX = translateX.value;
      ctx.startY = translateY.value;
    },
    onActive: (event, ctx) => {
      translateX.value = ctx.startX + event.translationX;
      translateY.value = ctx.startY + event.translationY;
    }
  });

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value }
      ]
    };
  });

  return (
    <PanGestureHandler onGestureEvent={panGestureEvent}>
      <Animated.View 
        style={[
          { width: 100, height: 100, backgroundColor: 'red' },
          animatedStyle
        ]} 
      />
    </PanGestureHandler>
  );
}

Gesture Composition

import { TapGestureHandler, LongPressGestureHandler, State } from 'react-native-gesture-handler';

function ComposedGestures() {
  const doubleTapRef = React.useRef();
  
  return (
    <LongPressGestureHandler
      onHandlerStateChange={({ nativeEvent }) => {
        if (nativeEvent.state === State.ACTIVE) {
          console.log('Long press detected');
        }
      }}
      minDurationMs={800}
    >
      <TapGestureHandler
        onHandlerStateChange={({ nativeEvent }) => {
          if (nativeEvent.state === State.ACTIVE) {
            console.log('Single tap detected');
          }
        }}
        waitFor={doubleTapRef}
      >
        <TapGestureHandler
          ref={doubleTapRef}
          onHandlerStateChange={({ nativeEvent }) => {
            if (nativeEvent.state === State.ACTIVE) {
              console.log('Double tap detected');
            }
          }}
          numberOfTaps={2}
        >
          <Animated.View style={{ backgroundColor: 'yellow', padding: 20 }}>
            <Text>This view responds to taps and long press</Text>
          </Animated.View>
        </TapGestureHandler>
      </TapGestureHandler>
    </LongPressGestureHandler>
  );
}

Gaming Application Patterns

Virtual Joystick Implementation

import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, { useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
import { View, StyleSheet } from 'react-native';

function VirtualJoystick({ onMove, size = 100, innerSize = 40 }) {
  const centerX = useSharedValue(size / 2);
  const centerY = useSharedValue(size / 2);
  const stickX = useSharedValue(size / 2);
  const stickY = useSharedValue(size / 2);
  
  const gestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = stickX.value;
      context.startY = stickY.value;
    },
    onActive: (event, context) => {
      // Calculate distance from center
      const dx = event.translationX;
      const dy = event.translationY;
      const distance = Math.sqrt(dx * dx + dy * dy);
      const maxDistance = (size - innerSize) / 2;
      
      // Normalize if exceeding max distance
      let moveX = dx;
      let moveY = dy;
      if (distance > maxDistance) {
        const angle = Math.atan2(dy, dx);
        moveX = Math.cos(angle) * maxDistance;
        moveY = Math.sin(angle) * maxDistance;
      }
      
      // Update stick position
      stickX.value = centerX.value + moveX;
      stickY.value = centerY.value + moveY;
      
      // Calculate normalized values for game input (-1 to 1)
      const normalizedX = moveX / maxDistance;
      const normalizedY = moveY / maxDistance;
      
      // Call the onMove callback with normalized values
      onMove?.(normalizedX, normalizedY);
    },
    onEnd: () => {
      // Return to center with spring animation
      stickX.value = withSpring(centerX.value);
      stickY.value = withSpring(centerY.value);
      onMove?.(0, 0);
    },
  });
  
  const baseStyle = {
    width: size,
    height: size,
    borderRadius: size / 2,
    backgroundColor: 'rgba(0,0,0,0.2)',
    justifyContent: 'center',
    alignItems: 'center',
  };
  
  const stickStyle = useAnimatedStyle(() => {
    return {
      width: innerSize,
      height: innerSize,
      borderRadius: innerSize / 2,
      backgroundColor: 'rgba(0,0,0,0.5)',
      transform: [
        { translateX: stickX.value - centerX.value },
        { translateY: stickY.value - centerY.value },
      ],
    };
  });
  
  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <Animated.View style={baseStyle}>
        <Animated.View style={stickStyle} />
      </Animated.View>
    </PanGestureHandler>
  );
}

Block Selection in 3D Space

import { TapGestureHandler, LongPressGestureHandler, State } from 'react-native-gesture-handler';
import { View } from 'react-native';
import { useRef } from 'react';

function BlockSelector({ onBlockSelect, onBlockDestroy }) {
  const tapRef = useRef();
  
  const handleTap = ({ nativeEvent }) => {
    if (nativeEvent.state === State.ACTIVE) {
      // Convert screen coordinates to ray in 3D space
      const touchX = nativeEvent.x;
      const touchY = nativeEvent.y;
      onBlockSelect(touchX, touchY);
    }
  };
  
  const handleLongPress = ({ nativeEvent }) => {
    if (nativeEvent.state === State.ACTIVE) {
      const touchX = nativeEvent.x;
      const touchY = nativeEvent.y;
      onBlockDestroy(touchX, touchY);
    }
  };
  
  return (
    <LongPressGestureHandler
      onHandlerStateChange={handleLongPress}
      minDurationMs={500}
      waitFor={tapRef}
    >
      <TapGestureHandler
        ref={tapRef}
        onHandlerStateChange={handleTap}
      >
        <View style={{ flex: 1 }} />
      </TapGestureHandler>
    </LongPressGestureHandler>
  );
}

Common Challenges and Solutions

Connections

References

  1. React Native Gesture Handler official documentation
  2. "Advanced Gesture-Based Interactions in React Native" by Software Mansion
  3. Community patterns from React Native game development forums

#react-native #gesture-handling #mobile-interactions #game-controls #touch-input


Connections:


Sources: