Skip to content

Instantly share code, notes, and snippets.

@terrysahaidak
Created March 27, 2020 12:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save terrysahaidak/03cedb182e4d4269b53b2e84bf106812 to your computer and use it in GitHub Desktop.
Save terrysahaidak/03cedb182e4d4269b53b2e84bf106812 to your computer and use it in GitHub Desktop.
Pinch gesture handling based on Image Viewer example implemented using Reanimated Worklets
import React, { useRef } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import Animated, {
useEventWorklet,
useSharedValue,
} from 'react-native-reanimated';
import {
PinchGestureHandler,
} from 'react-native-gesture-handler';
const width = 300;
const height = width;
const onScaleWorklet = function(
scale,
dimensions,
displacement,
scaleTopLeftFix,
translateX,
translateY
) {
'worklet';
// just helper
const GestureState = {
UNDETERMINED: this.event.state === 0,
FAILED: this.event.state === 1,
BEGAN: this.event.state === 2,
CANCELLED: this.event.state === 3,
ACTIVE: this.event.state === 4,
END: this.event.state === 5,
};
if (GestureState.BEGAN) {
// save previous value to find the nextScale
scale.prev.set(scale.current.value);
}
const MIN_SCALE = 1;
const MAX_SCALE = 3;
// find the nextScale, but bound it to be between 1 and 3
const nextScale = Math.max(
MIN_SCALE,
Math.min(MAX_SCALE, this.event.scale * scale.prev.value)
);
// define relative focal in order to allow scale to the focal point
// even after we already have some scale
const focalX =
this.event.focalX - (translateX.current.value + displacement.x.value);
const focalY =
this.event.focalY - (translateY.current.value + displacement.y.value);
if (GestureState.ACTIVE) {
displacement.x.set(
displacement.x.value - focalX * (-1 + nextScale / scale.current.value)
);
displacement.y.set(
displacement.y.value - focalY * (-1 + nextScale / scale.current.value)
);
scale.current.set(nextScale);
}
// make scale to be from 0,0
scaleTopLeftFix.x.set(
(dimensions.width.value * (scale.current.value - 1)) / 2
);
scaleTopLeftFix.y.set(
(dimensions.height.value * (scale.current.value - 1)) / 2
);
};
export default function ImageViewer() {
const pinchRef = useRef();
// for future pan gesture handler
const translateY = useSharedValue({ current: 0, prev: 0 });
const translateX = useSharedValue({ current: 0, prev: 0 });
const scale = useSharedValue({ prev: 1, current: 1 });
const focalDisplacement = useSharedValue({ x: 0, y: 0 });
const scaleTopLeftFix = useSharedValue({ x: 0, y: 0 });
const dimensions = useSharedValue({
width,
height,
});
const onScale = useEventWorklet(onScaleWorklet, [
scale,
dimensions,
focalDisplacement,
scaleTopLeftFix,
translateX,
translateY,
]);
return (
<Animated.View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Animated.View
style={{
backgroundColor: 'black',
flex: 1,
...StyleSheet.absoluteFillObject,
}}
/>
<View
style={{
borderColor: 'green',
borderWidth: 2,
overflow: 'hidden',
}}>
<PinchGestureHandler
ref={pinchRef}
onGestureEvent={onScale}
onHandlerStateChange={onScale}>
<Animated.View>
<Animated.View
style={{
width,
height,
transform: [
{ translateX: translateX.current },
{ translateY: translateY.current },
{ translateX: focalDisplacement.x },
{ translateY: focalDisplacement.y },
{ translateX: scaleTopLeftFix.x },
{ translateY: scaleTopLeftFix.y },
{ scale: scale.current },
],
}}>
<Grid />
</Animated.View>
</Animated.View>
</PinchGestureHandler>
</View>
</Animated.View>
);
}
ImageViewer.navigationOptions = () => ({
header: null,
});
const Grid = React.memo(() => {
const style = {
width: 20,
height: 20,
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'white',
backgroundColor: 'green',
justifyContent: 'center',
alignItems: 'center',
};
const textStyle = {
fontSize: 6,
color: 'white',
};
const arr = Array.from({ length: 15 * 15 }, (_, i) => (
<View key={i} style={style}>
<Text style={textStyle}>{i}</Text>
</View>
));
return <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>{arr}</View>;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment