Skip to content

Instantly share code, notes, and snippets.

@j-piasecki
Created December 22, 2021 13:21
Show Gist options
  • Select an option

  • Save j-piasecki/c820b5728951ceaa29f35fb68518e0b2 to your computer and use it in GitHub Desktop.

Select an option

Save j-piasecki/c820b5728951ceaa29f35fb68518e0b2 to your computer and use it in GitHub Desktop.
Touch events example - entire code
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
measure,
useAnimatedRef,
} from 'react-native-reanimated';
function isPointInside(x: number, y: number, view: React.RefObject<any>) {
'worklet';
const bounds = measure(view);
return (
x > bounds.pageX &&
y > bounds.pageY &&
x < bounds.pageX + bounds.width &&
y < bounds.pageY + bounds.height
);
}
function Disc(props: React.PropsWithChildren<{ size: number; color: string }>) {
const ballRef = useAnimatedRef();
const translation = useSharedValue({ x: 0, y: 0 });
const scale = useSharedValue(1);
const isPanned = useSharedValue(false);
const isPinched = useSharedValue(false);
const savedTranslation = useSharedValue({ x: 0, y: 0 });
const savedScale = useSharedValue(1);
const animatedStyles = useAnimatedStyle(() => {
return {
width: props.size,
height: props.size,
transform: [
{ translateX: translation.value.x },
{ translateY: translation.value.y },
{ scale: withSpring(scale.value) },
],
backgroundColor:
isPanned.value || isPinched.value ? '#FFF096' : props.color,
};
});
const pan = Gesture.Pan()
.manualActivation(true)
.averageTouches(true)
.onStart(() => {
isPanned.value = true;
})
.onUpdate((e) => {
translation.value = {
x: e.translationX + savedTranslation.value.x,
y: e.translationY + savedTranslation.value.y,
};
})
.onEnd(() => {
savedTranslation.value = {
x: translation.value.x,
y: translation.value.y,
};
})
.onFinalize(() => {
isPanned.value = false;
})
.onTouchesMove((e, state) => {
const x =
e.allTouches.reduce((prev, current) => prev + current.absoluteX, 0) /
e.numberOfTouches;
const y =
e.allTouches.reduce((prev, current) => prev + current.absoluteY, 0) /
e.numberOfTouches;
if (isPointInside(x, y, ballRef)) {
state.activate();
}
});
const pinch = Gesture.Pinch()
.onStart(() => {
isPinched.value = true;
})
.onUpdate((e) => {
scale.value = savedScale.value * e.scale;
})
.onEnd(() => {
savedScale.value = scale.value;
})
.onFinalize(() => {
isPinched.value = false;
})
.onTouchesDown((e, state) => {
if (e.numberOfTouches === 2) {
const x = (e.allTouches[0].absoluteX + e.allTouches[1].absoluteX) / 2;
const y = (e.allTouches[0].absoluteY + e.allTouches[1].absoluteY) / 2;
if (isPointInside(x, y, ballRef)) {
state.activate();
} else {
state.fail();
}
}
});
return (
<GestureDetector gesture={Gesture.Simultaneous(pinch, pan)}>
<Animated.View style={StyleSheet.absoluteFillObject}>
<Animated.View ref={ballRef} style={[styles.ball, animatedStyles]} />
{props.children}
</Animated.View>
</GestureDetector>
);
}
export default function Example() {
return (
<View style={styles.container}>
<Disc size={200} color="#001A72">
<Disc size={130} color="#A0D5EF">
<Disc size={60} color="#FFB2AD" />
</Disc>
</Disc>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
ball: {
borderRadius: 100,
alignSelf: 'center',
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment