Gesture System and Event Interaction
Gesture Recognition Basics
Tap Gesture Implementation
import React from 'react';
import { View, Text, TouchableOpacity, TouchableWithoutFeedback, StyleSheet } from 'react-native';
const TapGestureExample = () => {
const handleTap = () => {
console.log('Tap gesture detected!');
};
return (
<View style={styles.container}>
{/* Method 1: Using TouchableOpacity for simple tap */}
<TouchableOpacity onPress={handleTap} style={styles.button}>
<Text>TouchableOpacity Tap</Text>
</TouchableOpacity>
{/* Method 2: Using TouchableWithoutFeedback for no-feedback tap */}
<TouchableWithoutFeedback onPress={handleTap}>
<View style={styles.box}>
<Text>TouchableWithoutFeedback Tap</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
button: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 5,
marginBottom: 20,
},
box: {
width: 200,
height: 100,
backgroundColor: '#4CAF50',
justifyContent: 'center',
alignItems: 'center',
},
});
export default TapGestureExample;
Pan Gesture Implementation
import React, { useRef, useState } from 'react';
import { View, Text, PanResponder, StyleSheet } from 'react-native';
const PanGestureExample = () => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const panResponder = useRef(
PanResponder.create({
// Request to become responder
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
// Start dragging
onPanResponderGrant: () => {
console.log('Drag started');
},
// During drag
onPanResponderMove: (evt, gestureState) => {
setPosition({
x: gestureState.dx,
y: gestureState.dy,
});
},
// End drag
onPanResponderRelease: () => {
console.log('Drag ended');
},
})
).current;
return (
<View style={styles.container}>
<View
{...panResponder.panHandlers}
style={[
styles.box,
{
transform: [
{ translateX: position.x },
{ translateY: position.y },
],
},
]}
>
<Text>Drag me!</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
box: {
width: 100,
height: 100,
backgroundColor: '#FF5722',
justifyContent: 'center',
alignItems: 'center',
},
});
export default PanGestureExample;
Gesture Combinations and Conflict Resolution
Pinch Gesture Implementation
import React, { useRef, useState } from 'react';
import { View, Text, PanResponder, StyleSheet } from 'react-native';
const PinchGestureExample = () => {
const [scale, setScale] = useState(1);
const [lastDistance, setLastDistance] = useState(0);
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: (evt) => {
if (evt.nativeEvent.touches.length === 2) {
const dx = evt.nativeEvent.touches[0].pageX - evt.nativeEvent.touches[1].pageX;
const dy = evt.nativeEvent.touches[0].pageY - evt.nativeEvent.touches[1].pageY;
const distance = Math.sqrt(dx * dx + dy * dy);
setLastDistance(distance);
}
},
onPanResponderMove: (evt) => {
if (evt.nativeEvent.touches.length === 2) {
const dx = evt.nativeEvent.touches[0].pageX - evt.nativeEvent.touches[1].pageX;
const dy = evt.nativeEvent.touches[0].pageY - evt.nativeEvent.touches[1].pageY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (lastDistance > 0) {
const newScale = scale * (distance / lastDistance);
setScale(Math.min(Math.max(newScale, 0.5), 3)); // Limit scale range
}
setLastDistance(distance);
}
},
onPanResponderRelease: () => {
setLastDistance(0);
},
})
).current;
return (
<View style={styles.container}>
<View
{...panResponder.panHandlers}
style={[
styles.box,
{
transform: [{ scale }],
},
]}
>
<Text>Pinch to zoom</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
box: {
width: 200,
height: 200,
backgroundColor: '#9C27B0',
justifyContent: 'center',
alignItems: 'center',
},
});
export default PinchGestureExample;
Gesture Conflict Resolution
import React, { useRef, useState } from 'react';
import { View, Text, PanResponder, StyleSheet, TouchableOpacity } from 'react-native';
import { TapGestureHandler, PanGestureHandler, State } from 'react-native-gesture-handler';
const GestureConflictExample = () => {
const [tapCount, setTapCount] = useState(0);
const [panCount, setPanCount] = useState(0);
const tapResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => {
// Do not respond to tap if movement exceeds 5 pixels
return Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5;
},
onPanResponderGrant: () => {
// Use a short delay to confirm tap
setTimeout(() => {
if (Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5) {
setTapCount(tapCount + 1);
}
}, 100);
},
onPanResponderMove: () => {},
onPanResponderRelease: () => {},
})
).current;
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => {
// Respond to drag if movement exceeds 5 pixels
return Math.abs(gestureState.dx) >= 5 || Math.abs(gestureState.dy) >= 5;
},
onPanResponderMove: (evt, gestureState) => {
setPanCount(panCount + 1);
},
onPanResponderRelease: () => {},
})
).current;
const onTapEvent = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
setTapCount(tapCount + 1);
}
};
const onPanEvent = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
setPanCount(panCount + 1);
}
};
return (
<View style={styles.container}>
<View
style={styles.box}
{...tapResponder.panHandlers}
>
<Text>Tap me (count: {tapCount})</Text>
</View>
<View
style={[styles.box, { marginTop: 20 }]}
{...panResponder.panHandlers}
>
<Text>Drag me (count: {panCount})</Text>
</View>
{/* Method 2: Combining TouchableOpacity and Gesture Handler */}
<TapGestureHandler onHandlerStateChange={onTapEvent}>
<View style={[styles.box, { marginTop: 20 }]}>
<Text>Tap me (count: {tapCount})</Text>
</View>
</TapGestureHandler>
<PanGestureHandler onHandlerStateChange={onPanEvent}>
<View style={[styles.box, { marginTop: 20 }]}>
<Text>Drag me (count: {panCount})</Text>
</View>
</PanGestureHandler>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
box: {
width: 200,
height: 100,
backgroundColor: '#4CAF50',
justifyContent: 'center',
alignItems: 'center',
},
});
export default GestureConflictExample;
Advanced Gesture Interaction
ScrollView and Gesture Interaction
import React, { useRef, useState } from 'react';
import { View, Text, ScrollView, PanResponder, StyleSheet } from 'react-native';
const ScrollViewGestureExample = () => {
const scrollViewRef = useRef(null);
const [scrollEnabled, setScrollEnabled] = useState(true);
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => {
// Disable ScrollView scrolling when vertical movement is greater
if (Math.abs(gestureState.dy) > Math.abs(gestureState.dx)) {
setScrollEnabled(false);
return true;
}
return false;
},
onPanResponderGrant: () => {
setScrollEnabled(false);
},
onPanResponderRelease: () => {
setScrollEnabled(true);
},
onPanResponderTerminate: () => {
setScrollEnabled(true);
},
})
).current;
return (
<View style={styles.container}>
<ScrollView
ref={scrollViewRef}
scrollEnabled={scrollEnabled}
style={styles.scrollView}
>
{/* Long content */}
{Array.from({ length: 50 }).map((_, i) => (
<Text key={i} style={styles.text}>Item {i}</Text>
))}
</ScrollView>
<View
{...panResponder.panHandlers}
style={styles.overlay}
>
<Text>Drag to disable ScrollView scrolling</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollView: {
flex: 1,
},
text: {
padding: 20,
fontSize: 18,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
overlay: {
position: 'absolute',
top: 100,
left: 50,
right: 50,
height: 100,
backgroundColor: 'rgba(255,0,0,0.3)',
justifyContent: 'center',
alignItems: 'center',
},
});
export default ScrollViewGestureExample;
Gesture Sequence Recognition
import React, { useRef, useState } from 'react';
import { View, Text, PanResponder, StyleSheet } from 'react-native';
const GestureSequenceExample = () => {
const [sequence, setSequence] = useState([]);
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: (evt, gestureState) => {
setSequence(['Grant']);
},
onPanResponderMove: (evt, gestureState) => {
if (Math.abs(gestureState.dx) > Math.abs(gestureState.dy)) {
setSequence(prev => [...prev, 'Horizontal Move']);
} else {
setSequence(prev => [...prev, 'Vertical Move']);
}
},
onPanResponderRelease: (evt, gestureState) => {
setSequence(prev => [...prev, 'Release']);
// Recognize specific gesture sequence
if (sequence.join(',').includes('Grant,Horizontal Move,Release')) {
console.log('Horizontal swipe detected');
} else if (sequence.join(',').includes('Grant,Vertical Move,Release')) {
console.log('Vertical swipe detected');
}
// Reset sequence
setTimeout(() => setSequence([]), 1000);
},
})
).current;
return (
<View style={styles.container}>
<View
{...panResponder.panHandlers}
style={styles.box}
>
<Text>Perform gesture sequence</Text>
</View>
<Text>Current gesture sequence: {sequence.join(', ')}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
box: {
width: 200,
height: 200,
backgroundColor: '#FF9800',
justifyContent: 'center',
alignItems: 'center',
},
});
export default GestureSequenceExample;
Performance Optimization and Best Practices
Gesture Performance Optimization Techniques
import React, { useRef, memo } from 'react';
import { View, Text, PanResponder, StyleSheet } from 'react-native';
// Use memo to avoid unnecessary re-renders
const OptimizedGestureComponent = memo(() => {
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
// Use throttle to reduce event handling frequency
onPanResponderMove: throttle((evt, gestureState) => {
console.log('Throttled move event', gestureState.dx, gestureState.dy);
}, 16), // Approx. 60fps
})
).current;
return (
<View
{...panResponder.panHandlers}
style={styles.box}
>
<Text>Optimized Gesture</Text>
</View>
);
});
// Simple throttle function implementation
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function(...args) {
if (!lastRan) {
func.apply(this, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
}
}
const styles = StyleSheet.create({
box: {
width: 200,
height: 200,
backgroundColor: '#607D8B',
justifyContent: 'center',
alignItems: 'center',
},
});
export default OptimizedGestureComponent;
Gesture Recognition Best Practices
import React, { useRef, useState } from 'react';
import { View, Text, PanResponder, StyleSheet, Animated } from 'react-native';
const BestPracticeGestureExample = () => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const animatedValue = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
// Use animation for smooth movement
onPanResponderMove: (evt, gestureState) => {
animatedValue.x.setValue(gestureState.dx);
animatedValue.y.setValue(gestureState.dy);
},
onPanResponderRelease: () => {
// Add spring animation effect
Animated.spring(animatedValue, {
toValue: { x: 0, y: 0 },
friction: 4,
tension: 30,
useNativeDriver: true, // Use native driver for better performance
}).start();
},
})
).current;
// Bind animation value to position state
React.useEffect(() => {
animatedValue.addListener(({ x, y }) => {
setPosition({ x, y });
});
return () => {
animatedValue.removeAllListeners();
};
}, [animatedValue]);
return (
<View style={styles.container}>
<Animated.View
{...panResponder.panHandlers}
style=[
styles.box,
{
transform: [
{ translateX: animatedValue.x },
{ translateY: animatedValue.y },
],
},
]
>
<Text>Drag me with animation</Text>
</Animated.View>
<Text>Current position: x: {position.x.toFixed(2)}, y: {position.y.toFixed(2)}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
box: {
width: 150,
height: 150,
backgroundColor: '#E91E63',
justifyContent: 'center',
alignItems: 'center',
},
});
export default BestPracticeGestureExample;
Summary of Gesture System and Event Interaction
React Native provides various gesture recognition and handling methods, from simple taps and swipes to complex pinch and rotation gestures. Developers can choose the appropriate approach based on their needs:
- Simple Gestures: Use built-in components like
TouchableOpacityandTouchableWithoutFeedbackfor basic tap interactions. - Complex Gestures: Use the
PanResponderAPI for custom gesture recognition, such as dragging and zooming. - Advanced Gestures: Use the
react-native-gesture-handlerlibrary for complex gesture combinations and conflict resolution. - Performance Optimization:
- Use throttling or debouncing to reduce event handling frequency.
- Use the
AnimatedAPI for smooth animation effects. - Use
useNativeDriverto improve animation performance. - Avoid complex computations in gesture handler functions.
- Best Practices:
- Handle gesture conflicts appropriately to ensure a smooth user experience.
- Provide visual feedback for gesture operations.
- Consider compatibility with different devices and screen sizes.
- Use native drivers when appropriate to enhance performance.
By combining these techniques effectively, developers can build responsive, user-friendly interactive applications.



