diff --git a/src/features/PinchAnimation/index.tsx b/src/features/PinchAnimation/index.tsx new file mode 100644 index 0000000..4739f6d --- /dev/null +++ b/src/features/PinchAnimation/index.tsx @@ -0,0 +1,115 @@ +import React from 'react' +import { StyleSheet, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { Header } from '../../components/Header' +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + // runOnJS, +} from 'react-native-reanimated' +import { Gesture, GestureDetector } from 'react-native-gesture-handler' + +export const PinchAnimation = () => { + // Shared values for scale and pan + const scale = useSharedValue(1) + const savedScale = useSharedValue(1) + + const translateX = useSharedValue(0) + const translateY = useSharedValue(0) + const savedTranslateX = useSharedValue(0) + const savedTranslateY = useSharedValue(0) + + // --- Pinch gesture --- + const pinch = Gesture.Pinch() + .onBegin(() => { + savedScale.value = scale.value + }) + .onUpdate(e => { + scale.value = savedScale.value * e.scale + + // Calculate focal adjustment to keep the zoom centered on the focal point + const adjustedFocalX = e.focalX - 150 + const adjustedFocalY = e.focalY - 150 + + translateX.value = savedTranslateX.value + adjustedFocalX * (1 - e.scale) + translateY.value = savedTranslateY.value + adjustedFocalY * (1 - e.scale) + }) + .onEnd(() => { + // Clamp zoom out + if (scale.value < 1) { + scale.value = withTiming(1) + translateX.value = withTiming(0) + translateY.value = withTiming(0) + } + savedTranslateX.value = translateX.value + savedTranslateY.value = translateY.value + }) + + // --- Pan gesture --- + const pan = Gesture.Pan() + .onBegin(() => { + savedTranslateX.value = translateX.value + savedTranslateY.value = translateY.value + }) + .onUpdate(e => { + translateX.value = savedTranslateX.value + e.translationX + translateY.value = savedTranslateY.value + e.translationY + }) + .onEnd(() => { + savedTranslateX.value = translateX.value + savedTranslateY.value = translateY.value + }) + + // Combine pinch + pan + const gesture = Gesture.Simultaneous(pinch, pan) + + // Animated style + const animatedStyle = useAnimatedStyle(() => ({ + transform: [ + { translateX: translateX.value }, + { translateY: translateY.value }, + { scale: scale.value }, + ], + })) + return ( + +
+ + + + + + + + + ) +} + +const styles = StyleSheet.create({ + root: { + flex: 1, + backgroundColor: '#FDCFFA', + }, + container: { + flex: 1, + backgroundColor: '#111', + justifyContent: 'center', + alignItems: 'center', + }, + canvas: { + width: 300, + height: 300, + backgroundColor: '#333', + borderRadius: 10, + overflow: 'hidden', + justifyContent: 'center', + alignItems: 'center', + }, + child: { + width: 150, + height: 150, + backgroundColor: '#4caf50', + borderRadius: 10, + }, +}) diff --git a/src/navigation/NavigationStack.tsx b/src/navigation/NavigationStack.tsx index 5e38c93..e73625c 100644 --- a/src/navigation/NavigationStack.tsx +++ b/src/navigation/NavigationStack.tsx @@ -15,6 +15,7 @@ import AnimationsScreen from '../features/AnimationsScreen' import BottomSheetScreen from '../features/BottomSheets/BottomSheetScreen' import SwipeAnimation from '../features/SwipeAnimation' import { FloatingButton } from '../features/FloatingButton' +import { PinchAnimation } from '../features/PinchAnimation' const Stack = createNativeStackNavigator() const Drawer = createDrawerNavigator() @@ -27,6 +28,7 @@ const drawerItems = [ { key: 'BottomSheets', label: 'Bottom Sheets', screen: 'BottomSheetsScreen' }, { key: 'SwipeAnimation', label: 'Swipe Animation', screen: 'SwipeAnimation' }, { key: 'FloatingButton', label: 'Floating Button', screen: 'FloatingButton' }, + { key: 'PinchAnimation', label: 'Pinch Animation', screen: 'PinchAnimation' }, ] const CustomDrawerContent = () => { @@ -102,6 +104,7 @@ const HomeStackNavigator = () => ( + )