I have hit a bit of a wall and need some help figuring out how to pass a useState value into the OnStart hook of useAnimatedGestureHandler. My understanding of the issue is that useState exists on the Javascript thread while useAnimatedGestureHandler works on the Ui thread, so there are special techniques for passing information between them (useSharedValue, useDerivedValue, runOnJS, etc). But so far I've failed at every implementation I've tried.
The basic premise is that I have an array of "tiles" that one can drag and move around the screen. Each tile is a self-contained component which exports a PanGestureHandler with an Animated.View. I've been successful in getting this far, as each tile can maintain its own information about its X/Y coordinates, etc.
The trouble is that I need all of the tiles to tap into a single, global variable that stores an integer representing the highest zAxis on screen. With each tap or drag, that zAxis needs to increase by 1 and then be adopted by the currently-dragged tile so that whatever you're dragging always moves to the top of the zIndex stack. I imagine this value should be maintained with a useState hook on the parent container, but how exactly does one go about updating and then assigning this value at the onStart hook of the useAnimatedGestureHandler in the tile component?
Thanks in advance for any advice you have for me!
Regards, Jonathan
Here's the code I have so far. I've set up a useState variable called [maxZ, setMaxZ], but it's not currently being used. (I was also playing around with a more local variable called translateZ, but because the scope is inside the DraggableTile, that causes each tile to maintain its own max zIndex, so it's not working exactly as intended).
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedGestureHandler,
useDerivedValue,
runOnJS
} from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
const tileWidth = 100;
const tileHeight = 60;
export default function App() {
const context = {
x: 0,
y: 0
}
const [maxZ, setMaxZ] = useState(0);
const DraggableTile = ({txt}) => {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const currentZ = useSharedValue(0);
const panGestureEvent = useAnimatedGestureHandler({
onStart: (event, context) => {
currentZ.value++;
context.x = translateX.value;
context.y = translateY.value;
},
onActive: (event, context) => {
translateX.value = event.translationX + context.x;
translateY.value = event.translationY + context.y;
},
onEnd: (event) => {}
})
const repositionedStyle = useAnimatedStyle(() => {
return {
transform: [{translateX: translateX.value}, {translateY: translateY.value}],
zIndex: currentZ.value
}
});
return (
<PanGestureHandler onGestureEvent={panGestureEvent}>
<Animated.View style={[styles.tile, repositionedStyle]}>
<Text style={styles.tileText}>{txt}</Text>
</Animated.View>
</PanGestureHandler>
);
}
return (
<View style={styles.container}>
<DraggableTile txt="101" />
<DraggableTile txt="102" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ddd',
alignItems: 'center',
justifyContent: 'center',
},
tile: {
width: tileWidth,
height: tileHeight,
backgroundColor: "#fff",
borderWidth: 1,
borderColor: "#aaa",
display:"inline-flex",
flexDirection:"row",
alignItems:"center",
justifyContent:"center"
},
tileText: {
fontSize:28,
fontWeight:600
}
});