I have a FlatList carousel and I'm using react-native-gesture-handler to create a draggable zoom in effect for the image. The zoom in and dragging works, however I'm having problems setting a boundary for the image as it is overlapped by the next image once I zoom in and the dragging will go all the way to the end of the slider. I found a couple of examples where you can achieve this, but all of them were with react-native-reanimated. This is my code so far:
const { height, width } = Dimensions.get('window');
console.log("height", height, width)
const ITEM_HEIGHT = height * 0.7
const ITEM_WIDTH = height * 0.46
const Preview = ({ navigation, route }) => {
const { item, goBack } = route.params;
const buttonRef = React.useRef();
const ref = useRef()
const selectedPhotoIndex = data.findIndex((i) => i.id === item.id)
const scrollX = useRef(new Animated.Value(selectedPhotoIndex)).current
const scale = useRef(new Animated.Value(1)).current
const panRef = useRef()
const pinchRef = useRef()
const translationX = useRef(new Animated.Value(0)).current
const translationY = useRef(new Animated.Value(0)).current
const [scrollEnabled, setSCrollEnabled] = useState(true)
const goToIndex = useCallback((info) => {
const wait = new Promise(resolve => setTimeout(resolve, 200));
wait.then(() => {
ref.current?.scrollToIndex({ index: info.index, animated: true });
})
})
const pinchEvent = Animated.event([{ nativeEvent: { scale: scale } }], { useNativeDriver: true })
const onStateChange = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
setSCrollEnabled(false)
}
const nScale = event.nativeEvent.scale
if (event.nativeEvent.state === State.END || event.nativeEvent.oldState === State.ACTIVE) {
if (nScale < 1) {
Animated.spring(scale, {
toValue: 1,
useNativeDriver: true
}).start()
translationX.setOffset(0)
translationY.setOffset(0)
setSCrollEnabled(true)
}
} else if (event.nativeEvent.state === State.ACTIVE) {
setSCrollEnabled(false)
}
}
const panEvent = Animated.event([{
nativeEvent: {
translationX,
translationY
}
}], { useNativeDriver: true })
const onChange = (event) => {
translationX.extractOffset()
translationY.extractOffset()
if (event.nativeEvent.oldState === State.ACTIVE) {
}
}
const RenderItem = ({ item, translateX, viewLimit }) => {
return (
<View style={{ height, justifyContent: 'center', alignItems: 'center', alignContent: 'center' }}>
<PanGestureHandler failOffsetY={[-500, 500]}
failOffsetX={[-500, 500]}
enabled={!scrollEnabled}
ref={panRef}
simultaneousHandlers={[pinchRef]}
onHandlerStateChange={onChange}
onGestureEvent={panEvent}>
<Animated.View style={{
height, transform: [
{ translateX },
{ scale }
],
}}>
<SharedElement id={`item.${item.id}.image_url`}>
<PinchGestureHandler ref={pinchRef}
simultaneousHandlers={[panRef]}
onGestureEvent={pinchEvent}
onHandlerStateChange={onStateChange}>
<Animated.Image
source={{ uri: item.image_url }}
style={{
width: ITEM_WIDTH,
height: ITEM_HEIGHT,
transform: [
{ translateX: translationX },
{ translateY: translationY }
]
}}
resizeMode='contain'
/>
</PinchGestureHandler>
</SharedElement>
</Animated.View>
</PanGestureHandler>
<View
style={{ flexDirection: 'row', marginTop: 10, paddingHorizontal: 20 }}
>
<View style={{ flexDirection: 'column', paddingLeft: 6 }}>
<SharedElement id={`item.${item.id}.title`}>
<Text
style={{
color: 'white',
fontSize: 24,
fontWeight: 'bold',
lineHeight: 28
}}
>
{item.title}
</Text>
</SharedElement>
<SharedElement id={`item.${item.id}.description`}>
<Text
style={{
color: 'white',
fontSize: 16,
fontWeight: 'bold',
lineHeight: 18
}}
>
{item.description}
</Text>
</SharedElement>
</View>
</View>
</View>
)
}
return (
<View style={{ flex: 1, backgroundColor: '#0f0f0f', overflow: 'hidden' }}>
<BackButton navigation={navigation} />
<Animated.FlatList
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: true })}
pagingEnabled
nestedScrollEnabled={true}
snapToInterval={ITEM_WIDTH}
decelerationRate="normal"
scrollEnabled={scrollEnabled}
horizontal
ref={ref}
data={data}
keyExtractor={(item) => item.id}
initialScrollIndex={selectedPhotoIndex}
onScrollToIndexFailed={info => goToIndex(info)}
renderItem={({ item: details, index }) => {
const inputRange = [
(index - 1) * ITEM_WIDTH,
index * ITEM_WIDTH,
(index + 1) * ITEM_WIDTH
]
const swipeX = scrollX.interpolate({
inputRange,
outputRange: [ITEM_WIDTH, 0, -ITEM_WIDTH]
})
const viewLimit = translationX.interpolate({
inputRange,
outputRange: [ITEM_WIDTH, 0, -ITEM_WIDTH / 2]
})
return (
<RenderItem viewLimit={viewLimit} translateX={swipeX} item={details} />
)
}} />
</View>
);
};
How I can zoom in and contain the image dragging within the view, without the image being overlapped by the others?I've tried to change the gesture handler based on this topic https://github.com/software-mansion/react-native-gesture-handler/issues/110 but it didn't work for me. Any help would be appreciated!