What's up mobile devs? Today we're going to understand the basics of the PanGestureHandler component from the react-native-gesture-handler package. We'll also play around with the animations by using Reanimated v2.
The result will be this nice and clean animation composed of two elements:
A Circle 🟣
A Square 🟪
The purpose of this animation is to be able to drag the square in and out of the circle:
When the square is released inside the circle the spring animation will be handled
When the square is released outside the circle the square stays in its position
YouTube Channel
What about the same tutorial as a video? 🙄
Project setup
I've built up an Expo project with the Expo CLI and I've included the Reanimated library.
Let's start to place the square in the center of the screen.
...
constSIZE=100.0;
exportdefaultfunctionApp(){
return(
<GestureHandlerRootViewstyle={{flex:1}}>
<Viewstyle={styles.container}>
<Viewstyle={styles.square}/>
</View>
</GestureHandlerRootView>
);
}
const styles =StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
},
square:{
width:SIZE,
height:SIZE,
backgroundColor:'rgba(0,0,255,0.5)',
borderRadius:20// just for fun :)
}
});
Let's define the PanGestureHandler
We need to move around the square: how to do it? Of course, we need to import our Pan Gesture Handler from react-native-gesture-handler and we need to wrap the square with it.
Of course, right now we cannot drag our square. To do it we need to provide the onGestureEvent property to the Pan Handler.
<PanGestureHandleronGestureEvent={🧐🧐🧐}>
...
</PanGestureHandler>
How to do it? The answer to this question hides behind the useAnimatedGestureHandler hook from react-native-reanimated. This hook is quite magical. It could be really helpful to handle much more than a PanGesture.
The purpose of this hook is to provide us with some event callbacks:
At this point, we're able to drag around the square. On the first try, everything seems to work perfectly but on the second try we can already feel that something is going wrong 🕵🏼♀️.
The point is that we're not keeping the previous position. So on the second try, the translateX Shared Value will start again from the initial position. To fix this behavior we need to deal with the context object.
How to deal with "Context"?
The context is nothing more than a simple object where we can easily store stuff. In our case, we want to store, for now, just the previous translateX position.
We can access the context object from the useAnimatedGestureHandler hook as the second parameter of each callback.
...
const panGestureEvent =useAnimatedGestureHandler<
PanGestureHandlerGestureEvent
>({
onStart:(_, context)=>{},
onActive:(event, context)=>{
translateX.value= event.translationX
},
onEnd:()=>{},
});
...
Now that we know what's the context, we can use it to handle the translation state. The steps are the following:
Store the previous translateX.value position in the context.translateX
Apply the previous position to the current translateX.value
If you reload you can see that everything is working nicely but TypeScript is complaining about the context.translateX. That's because we need to specify the Context Type in the useAnimatedGestureHandler hook:
const panGestureEvent =useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
🦄🦄🦄
>({
...
})
Let's fix it by creating the ContextType:
typeContextType={
translateX:number;
};
exportdefaultfunctionApp(){
...
const panGestureEvent =useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
ContextType
>({
...
})
...
}
...
To deal with the y-axis we just need to replicate this code. As before...
Define our translateY as a SharedValue with an initial value equal to zero
Handle the onActive callback
Handle the onStart callback
Add the translateY type in the ContextType
Add the translateY SharedValue in the Reanimated Style
Let's start with the spring animation. The purpose is to handle the spring animation when we release the finger from the screen. To handle this use case we just need to handle the "onEnd" callback.
In particular, we need, for now, to set inside the "onEnd" callback both the translateX and translateY values equal to zero.
Right now, if you run the code, you'll see that the animation looks a little bit glitchy. That's kind of expected since we need to add the "spring effect" by simple wrapping the zero value with the "withSpring" high order function (from Reanimated)
If you're not familiar with these kinds of utilities like "withTiming", "withSpring" and so on, I highly recommend you to check out my previous video about Reanimated.
Run again... And everything should be fine 😎
Outer Circle Stroke
Our last step is to handle the outer circle stroke. To create this stroke we're just going to use a simple View component from react-native.
So... Let's wrap our PanGestureHandler with a View from 'react-native'
To be fully precise, we need to cover an additional edge case... In particular, when we release the finger and the square is partially outside, it must always return to the origin.
To handle this use case we must check an additional distance given by half of the square size...