xref: /expo/docs/pages/tutorial/gestures.mdx (revision 72bb203a)
1f2fad9b1SAman Mittal---
2f2fad9b1SAman Mittaltitle: Add gestures
3f2fad9b1SAman Mittal---
4f2fad9b1SAman Mittal
5f2fad9b1SAman Mittalimport { SnackInline, Terminal } from '~/ui/components/Snippet';
6f2fad9b1SAman Mittalimport Video from '~/components/plugins/Video';
73c9a6b96SBartosz Kaszubowskiimport { A } from '~/ui/components/Text';
8a30150b3SAman Mittalimport { Step } from '~/ui/components/Step';
99de0c686SAman Mittalimport { BoxLink } from '~/ui/components/BoxLink';
109de0c686SAman Mittalimport { BookOpen02Icon } from '@expo/styleguide-icons';
11f2fad9b1SAman Mittal
129de0c686SAman MittalGestures are a great way to provide an intuitive user experience in an app. The [React Native Gesture Handler library](https://docs.swmansion.com/react-native-gesture-handler/docs/) provides built-in native components that can handle gestures.
133c9a6b96SBartosz KaszubowskiIt uses the platform's native touch handling system to recognize pan, tap, rotation, and other gestures.
14f2fad9b1SAman Mittal
15f2fad9b1SAman MittalIn this chapter, we are going to add two different gestures using the React Native Gesture Handler library:
16f2fad9b1SAman Mittal
17f2fad9b1SAman Mittal- Double tap to scale the size of the emoji sticker.
18f2fad9b1SAman Mittal- Pan to move the emoji sticker around the screen so that the user can place the sticker anywhere on the image.
19f2fad9b1SAman Mittal
20a30150b3SAman Mittal<Step label="1">
21a30150b3SAman Mittal
22a30150b3SAman Mittal## Install and configure libraries
23f2fad9b1SAman Mittal
243c9a6b96SBartosz KaszubowskiThe React Native Gesture Handler library provides a way to interact with the native platform's gesture response system.
25*72bb203aSDavid LeulietteTo animate between gesture states, we will use the [Reanimated library](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/handling-gestures).
26f2fad9b1SAman Mittal
27f2fad9b1SAman MittalTo install them, stop the development server by pressing <kbd>Ctrl</kbd> + <kbd>c</kbd> and run the following command in the terminal:
28f2fad9b1SAman Mittal
29f2fad9b1SAman Mittal<Terminal cmd={['$ npx expo install react-native-gesture-handler react-native-reanimated']} />
30f2fad9b1SAman Mittal
31f2fad9b1SAman MittalNext, also install `@babel/plugin-proposal-export-namespace-from`, which is required to configure the Reanimated library:
32f2fad9b1SAman Mittal
33f2fad9b1SAman Mittal<Terminal cmd={['$ npm install -D @babel/plugin-proposal-export-namespace-from ']} />
34f2fad9b1SAman Mittal
35f2fad9b1SAman MittalThen, add Reanimated's Babel plugin to **babel.config.js**:
36f2fad9b1SAman Mittal
37f2fad9b1SAman Mittal{/* prettier-ignore */}
38f2fad9b1SAman Mittal```jsx babel.config.js
39f2fad9b1SAman Mittalmodule.exports = function (api) {
40f2fad9b1SAman Mittal  api.cache(true);
41f2fad9b1SAman Mittal  return {
42f2fad9b1SAman Mittal    presets: ['babel-preset-expo'],
43f2fad9b1SAman Mittal    /* @info Add the plugins array and inside it, add the plugins.*/
44f2fad9b1SAman Mittal    plugins: [
45f2fad9b1SAman Mittal      "@babel/plugin-proposal-export-namespace-from",
46f2fad9b1SAman Mittal      "react-native-reanimated/plugin",
47f2fad9b1SAman Mittal    ],
48f2fad9b1SAman Mittal    /* @end */
49f2fad9b1SAman Mittal  };
50f2fad9b1SAman Mittal};
51f2fad9b1SAman Mittal```
52f2fad9b1SAman Mittal
53f2fad9b1SAman MittalNow, start the development server again:
54f2fad9b1SAman Mittal
55f2fad9b1SAman Mittal<Terminal cmd={['$ npx expo start -c']} />
56f2fad9b1SAman Mittal
57f2fad9b1SAman Mittal> **Tip**: We are using `-c` option here because we modified the **babel.config.js** file.
58f2fad9b1SAman Mittal
59f2fad9b1SAman MittalTo get gesture interactions to work in the app, we'll render `<GestureHandlerRootView>` from `react-native-gesture-handler` to wrap the top-level component of our app (also known as the "root component").
60f2fad9b1SAman Mittal
61f2fad9b1SAman MittalTo accomplish this, replace the root level `<View>` component in the **App.js** with `<GestureHandlerRootView>`.
62f2fad9b1SAman Mittal
63f2fad9b1SAman Mittal{/* prettier-ignore */}
64f2fad9b1SAman Mittal```jsx App.js
65f2fad9b1SAman Mittal/* @info Import GestureHandlerRootView from react-native-gesture-handler-library. */import { GestureHandlerRootView } from "react-native-gesture-handler"; /* @end */
66f2fad9b1SAman Mittal
67f2fad9b1SAman Mittalexport default function App() {
68f2fad9b1SAman Mittal  return (
69f2fad9b1SAman Mittal    /* @info Replace the root level View component with GestureHandlerRootView. */<GestureHandlerRootView style={styles.container}> /* @end */
70f2fad9b1SAman Mittal    /* ...rest of the code remains */
71f2fad9b1SAman Mittal    /* @info */</GestureHandlerRootView>/* @end */
72f2fad9b1SAman Mittal  )
73f2fad9b1SAman Mittal}
74f2fad9b1SAman Mittal```
75f2fad9b1SAman Mittal
76a30150b3SAman Mittal</Step>
77a30150b3SAman Mittal
78a30150b3SAman Mittal<Step label="2">
79a30150b3SAman Mittal
80a30150b3SAman Mittal## Create animated components
81f2fad9b1SAman Mittal
82f2fad9b1SAman MittalOpen the **EmojiSticker.js** file in the **components** directory. Inside it, import `Animated` from the `react-native-reanimated` library to create animated components.
83f2fad9b1SAman Mittal
84f2fad9b1SAman Mittal```jsx EmojiSticker.js
85f2fad9b1SAman Mittalimport Animated from 'react-native-reanimated';
86f2fad9b1SAman Mittal```
87f2fad9b1SAman Mittal
88f2fad9b1SAman MittalTo make a double tap gesture work, we will apply animations to the `<Image>` component by passing it as an argument to the `Animated.createAnimatedComponent()` method.
89f2fad9b1SAman Mittal
90f2fad9b1SAman Mittal```jsx EmojiSticker.js
91f2fad9b1SAman Mittal// after import statements, add the following line
92f2fad9b1SAman Mittal
93f2fad9b1SAman Mittalconst AnimatedImage = Animated.createAnimatedComponent(Image);
94f2fad9b1SAman Mittal```
95f2fad9b1SAman Mittal
96f2fad9b1SAman MittalThe `createAnimatedComponent()` can wrap any component. It looks at the `style` prop of the component, determines which value is animated, and then applies updates to create an animation.
97f2fad9b1SAman Mittal
98f2fad9b1SAman MittalReplace the `<Image>` component with `<AnimatedImage>`.
99f2fad9b1SAman Mittal
100f2fad9b1SAman Mittal{/* prettier-ignore */}
101f2fad9b1SAman Mittal```jsx EmojiSticker.js
102f2fad9b1SAman Mittalexport default function EmojiSticker({ imageSize, stickerSource }) {
103f2fad9b1SAman Mittal  return (
104f2fad9b1SAman Mittal    <View style={{ top: -350 }}>
105f2fad9b1SAman Mittal      /* @info Replace the Image component with AnimatedImage. */<AnimatedImage /* @end */
106f2fad9b1SAman Mittal        source={stickerSource}
107f2fad9b1SAman Mittal        resizeMode="contain"
108f2fad9b1SAman Mittal        style={{ width: imageSize, height: imageSize }}
109f2fad9b1SAman Mittal      />
110f2fad9b1SAman Mittal    </View>
111f2fad9b1SAman Mittal  );
112f2fad9b1SAman Mittal}
113f2fad9b1SAman Mittal```
114f2fad9b1SAman Mittal
115a30150b3SAman Mittal</Step>
116a30150b3SAman Mittal
117a30150b3SAman Mittal<Step label="3">
118a30150b3SAman Mittal
119a30150b3SAman Mittal## Add a tap gesture
120f2fad9b1SAman Mittal
121f2fad9b1SAman MittalReact Native Gesture Handler allows us to add behavior when it detects touch input, like a double tap event.
122f2fad9b1SAman Mittal
1233c9a6b96SBartosz KaszubowskiIn the **EmojiSticker.js** file, import `TapGestureHandler` from `react-native-gesture-handler` and the hooks below from `react-native-reanimated`.
1243c9a6b96SBartosz KaszubowskiThese hooks will animate the style on the `<AnimatedImage>` component for the sticker when the tap gesture is recognized.
125f2fad9b1SAman Mittal
126f2fad9b1SAman Mittal```jsx EmojiSticker.js
127f2fad9b1SAman Mittalimport { TapGestureHandler } from 'react-native-gesture-handler';
128f2fad9b1SAman Mittalimport Animated, {
129f2fad9b1SAman Mittal  useAnimatedStyle,
130f2fad9b1SAman Mittal  useSharedValue,
131f2fad9b1SAman Mittal  useAnimatedGestureHandler,
132f2fad9b1SAman Mittal  withSpring,
133f2fad9b1SAman Mittal} from 'react-native-reanimated';
134f2fad9b1SAman Mittal```
135f2fad9b1SAman Mittal
136f2fad9b1SAman MittalInside the `<EmojiSticker>` component, create a reference called `scaleImage` using the `useSharedValue()` hook. It will take the value of `imageSize` as its initial value.
137f2fad9b1SAman Mittal
138f2fad9b1SAman Mittal```jsx EmojiSticker.js
139f2fad9b1SAman Mittalconst scaleImage = useSharedValue(imageSize);
140f2fad9b1SAman Mittal```
141f2fad9b1SAman Mittal
1423c9a6b96SBartosz KaszubowskiCreating a shared value using the `useSharedValue()` hook has many advantages. It helps to mutate a piece of data and allows running animations based on the current value.
1433c9a6b96SBartosz KaszubowskiA shared value can be accessed and modified using the `.value` property. It will scale the initial value of `scaleImage` so that when a user double-taps the sticker,
1443c9a6b96SBartosz Kaszubowskiit scales to twice its original size. To do this, we will create a function and call it `onDoubleTap()`, and this function will use the `useAnimatedGestureHandler()` hook
1453c9a6b96SBartosz Kaszubowskito animate the transition while scaling the sticker image.
146f2fad9b1SAman Mittal
147f2fad9b1SAman MittalCreate the following function in the `<EmojiSticker>` component:
148f2fad9b1SAman Mittal
149f2fad9b1SAman Mittal```jsx EmojiSticker.js
150f2fad9b1SAman Mittalconst onDoubleTap = useAnimatedGestureHandler({
151f2fad9b1SAman Mittal  onActive: () => {
15261b5371bSAman Mittal    if (scaleImage.value !== imageSize * 2) {
153f2fad9b1SAman Mittal      scaleImage.value = scaleImage.value * 2;
154f2fad9b1SAman Mittal    }
155f2fad9b1SAman Mittal  },
156f2fad9b1SAman Mittal});
157f2fad9b1SAman Mittal```
158f2fad9b1SAman Mittal
1593c9a6b96SBartosz KaszubowskiTo animate the transition, let's use a spring-based animation. This will make it feel alive because it's based on the real-world physics of a spring.
1603c9a6b96SBartosz KaszubowskiWe will use the `withSpring()` hook provided by `react-native-reanimated`.
161f2fad9b1SAman Mittal
1623c9a6b96SBartosz KaszubowskiThe `useAnimatedStyle()` hook from `react-native-reanimated` is used to create a style object that will be applied to the sticker image.
1633c9a6b96SBartosz KaszubowskiIt will update styles using the shared values when the animation happens. In this case, we are scaling the size of the image,
1643c9a6b96SBartosz Kaszubowskiwhich is done by manipulating the `width` and `height` properties. The initial values of these properties are set to `imageSize`.
1653c9a6b96SBartosz KaszubowskiCreate an `imageStyle` variable and add it to the `EmojiSticker` component:
166f2fad9b1SAman Mittal
167f2fad9b1SAman Mittal```jsx EmojiSticker.js
168f2fad9b1SAman Mittalconst imageStyle = useAnimatedStyle(() => {
169f2fad9b1SAman Mittal  return {
170f2fad9b1SAman Mittal    width: withSpring(scaleImage.value),
171f2fad9b1SAman Mittal    height: withSpring(scaleImage.value),
172f2fad9b1SAman Mittal  };
173f2fad9b1SAman Mittal});
174f2fad9b1SAman Mittal```
175f2fad9b1SAman Mittal
176f2fad9b1SAman MittalNext, wrap the `<AnimatedImage>` component that displays the sticker on the screen with the `<TapGestureHandler>` component.
177f2fad9b1SAman Mittal
178f2fad9b1SAman Mittal<SnackInline
179f2fad9b1SAman Mittallabel="Handling tap gesture"
180f2fad9b1SAman MittaltemplateId="tutorial/06-gestures/App"
181f2fad9b1SAman Mittaldependencies={['expo-image-picker', '@expo/vector-icons/FontAwesome', '@expo/vector-icons', 'expo-status-bar', '@expo/vector-icons/MaterialIcons', 'react-native-gesture-handler', 'react-native-reanimated']}
182f2fad9b1SAman Mittalfiles={{
183f2fad9b1SAman Mittal  'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910',
184f2fad9b1SAman Mittal  'assets/images/emoji1.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/be9751678c0b3f9c6bf55f60de815d30',
185f2fad9b1SAman Mittal  'assets/images/emoji2.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/7c0d14b79e134d528c5e0801699d6ccf',
186f2fad9b1SAman Mittal  'assets/images/emoji3.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/d713e2de164764c2ab3db0ab4e40c577',
187f2fad9b1SAman Mittal  'assets/images/emoji4.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ac2163b98a973cb50bfb716cc4438f9a',
188f2fad9b1SAman Mittal  'assets/images/emoji5.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/9cc0e2ff664bae3af766b9750331c3ad',
189f2fad9b1SAman Mittal  'assets/images/emoji6.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ce614cf0928157b3f7daa3cb8e7bd486',
190f2fad9b1SAman Mittal  'components/ImageViewer.js': 'tutorial/02-image-picker/ImageViewer.js',
191f2fad9b1SAman Mittal  'components/Button.js': 'tutorial/03-button-options/Button.js',
192f2fad9b1SAman Mittal  'components/CircleButton.js': 'tutorial/03-button-options/CircleButton.js',
193f2fad9b1SAman Mittal  'components/IconButton.js': 'tutorial/03-button-options/IconButton.js',
194f2fad9b1SAman Mittal  'components/EmojiPicker.js': 'tutorial/04-modal/EmojiPicker.js',
195f2fad9b1SAman Mittal  'components/EmojiList.js': 'tutorial/05-emoji-list/EmojiList.js',
196f2fad9b1SAman Mittal  'components/EmojiSticker.js': 'tutorial/06-gestures/EmojiSticker.js',
197f2fad9b1SAman Mittal}}>
198f2fad9b1SAman Mittal
199f2fad9b1SAman Mittal{/* prettier-ignore */}
200f2fad9b1SAman Mittal```jsx
201f2fad9b1SAman Mittalexport default function EmojiSticker({ imageSize, stickerSource }) {
202f2fad9b1SAman Mittal  // ...rest of the code remains same
203f2fad9b1SAman Mittal  return (
204f2fad9b1SAman Mittal    <View style={{ top: -350 }}>
205f2fad9b1SAman Mittal      /* @info Wrap the AnimatedImage component with TapGestureHandler*/ <TapGestureHandler onGestureEvent={onDoubleTap} numberOfTaps={2}>/* @end */
206f2fad9b1SAman Mittal        <AnimatedImage
207f2fad9b1SAman Mittal          source={stickerSource}
208f2fad9b1SAman Mittal          resizeMode="contain"
209f2fad9b1SAman Mittal          /* @info Modify the style prop on the AnimatedImage to pass the imageStyle.*/ style={[imageStyle, { width: imageSize, height: imageSize }]} /* @end */
210f2fad9b1SAman Mittal        />
211f2fad9b1SAman Mittal      /* @info */</TapGestureHandler>/* @end */
212f2fad9b1SAman Mittal    </View>
213f2fad9b1SAman Mittal  );
214f2fad9b1SAman Mittal}
215f2fad9b1SAman Mittal```
216f2fad9b1SAman Mittal
217f2fad9b1SAman Mittal</SnackInline>
218f2fad9b1SAman Mittal
2193c9a6b96SBartosz KaszubowskiIn the above snippet, the `onGestureEvent` prop takes the value of the `onDoubleTap()` function and triggers it when a user taps the sticker image.
2203c9a6b96SBartosz KaszubowskiThe `numberOfTaps` prop determines how many taps are required.
221f2fad9b1SAman Mittal
222f2fad9b1SAman MittalLet's take a look at our app on iOS, Android and the web:
223f2fad9b1SAman Mittal
224f2fad9b1SAman Mittal<Video file="tutorial/tap-gesture.mp4" />
225f2fad9b1SAman Mittal
2263c9a6b96SBartosz Kaszubowski> For a complete reference on the tap gesture API, refer to the [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/api/gestures/tap-gesture) documentation.
227f2fad9b1SAman Mittal
228a30150b3SAman Mittal</Step>
229a30150b3SAman Mittal
230a30150b3SAman Mittal<Step label="4">
231a30150b3SAman Mittal
232a30150b3SAman Mittal## Add a pan gesture
233f2fad9b1SAman Mittal
234f2fad9b1SAman MittalA pan gesture allows recognizing a dragging gesture and tracking its movement. We will use this gesture handler to drag the sticker across the image.
235f2fad9b1SAman Mittal
236f2fad9b1SAman MittalIn the **EmojiSticker.js**, import `PanGestureHandler` from the `react-native-gesture-handler` library.
237f2fad9b1SAman Mittal
238f2fad9b1SAman Mittal{/* prettier-ignore */}
239f2fad9b1SAman Mittal```jsx EmojiSticker.js
240f2fad9b1SAman Mittalimport { /* @info */ PanGestureHandler,/* @end */ TapGestureHandler} from "react-native-gesture-handler";
241f2fad9b1SAman Mittal```
242f2fad9b1SAman Mittal
243f2fad9b1SAman MittalCreate an `<AnimatedView>` component using the `Animated.createAnimatedComponent()` method. Then use it to wrap the `<TapGestureHandler>` component by replacing the `<View>` component.
244f2fad9b1SAman Mittal
245f2fad9b1SAman Mittal{/* prettier-ignore */}
246f2fad9b1SAman Mittal```jsx EmojiSticker.js
247f2fad9b1SAman Mittal// ...rest of the code remains same
248f2fad9b1SAman Mittal/* @info */ const AnimatedView = Animated.createAnimatedComponent(View); /* @end */
249f2fad9b1SAman Mittal
250f2fad9b1SAman Mittalexport default function EmojiSticker({ imageSize, stickerSource }) {
251f2fad9b1SAman Mittal  // ...rest of the code remains same
252f2fad9b1SAman Mittal
253f2fad9b1SAman Mittal  return (
254f2fad9b1SAman Mittal    /* @info Replace the View component with AnimatedView */<AnimatedView style={{ top: -350 }}>/* @end */
255f2fad9b1SAman Mittal      <TapGestureHandler onGestureEvent={onDoubleTap} numberOfTaps={2}>
256f2fad9b1SAman Mittal        {/* ...rest of the code remains same */}
257f2fad9b1SAman Mittal      </TapGestureHandler>
258f2fad9b1SAman Mittal    /* @info */</AnimatedView>/* @end */
259f2fad9b1SAman Mittal  );
260f2fad9b1SAman Mittal}
261f2fad9b1SAman Mittal```
262f2fad9b1SAman Mittal
263f2fad9b1SAman MittalNow, create two new shared values: `translateX` and `translateY`.
264f2fad9b1SAman Mittal
265f2fad9b1SAman Mittal```jsx EmojiSticker.js
266f2fad9b1SAman Mittalexport default function EmojiSticker({ imageSize, stickerSource }) {
267f2fad9b1SAman Mittal  const translateX = useSharedValue(0);
268f2fad9b1SAman Mittal  const translateY = useSharedValue(0);
269f2fad9b1SAman Mittal
270f2fad9b1SAman Mittal  // ...rest of the code remains same
271f2fad9b1SAman Mittal}
272f2fad9b1SAman Mittal```
273f2fad9b1SAman Mittal
274f2fad9b1SAman MittalThese translation values will move the sticker around the screen. Since the sticker moves along both axes, we need to track the X and Y values separately.
275f2fad9b1SAman Mittal
2763c9a6b96SBartosz KaszubowskiIn the `useSharedValue()` hooks, we have set both translation variables to have an initial position of `0`.
2773c9a6b96SBartosz KaszubowskiThis means that the position the sticker is initially placed is considered the starting point. This value sets the initial position of the sticker when the gesture starts.
278f2fad9b1SAman Mittal
2793c9a6b96SBartosz KaszubowskiIn the previous step, we triggered the `onActive()` callback for the tap gesture inside the `useAnimatedGestureHandler()` function.
2803c9a6b96SBartosz KaszubowskiSimilarly, for the pan gesture, we have to specify two callbacks:
281f2fad9b1SAman Mittal
282f2fad9b1SAman Mittal- `onStart()`: when the gesture starts or is at its initial position
283f2fad9b1SAman Mittal- `onActive()`: when the gesture is active and is moving
284f2fad9b1SAman Mittal
285f2fad9b1SAman MittalCreate an `onDrag()` method to handle the pan gesture.
286f2fad9b1SAman Mittal
287f2fad9b1SAman Mittal{/* prettier-ignore */}
288f2fad9b1SAman Mittal```jsx EmojiSticker.js
289f2fad9b1SAman Mittalconst onDrag = useAnimatedGestureHandler({
290f2fad9b1SAman Mittal  onStart: (event, context) => {
291f2fad9b1SAman Mittal    context.translateX = translateX.value;
292f2fad9b1SAman Mittal    context.translateY = translateY.value;
293f2fad9b1SAman Mittal  },
294f2fad9b1SAman Mittal  onActive: (event, context) => {
295f2fad9b1SAman Mittal    translateX.value = event.translationX + context.translateX;
296f2fad9b1SAman Mittal    translateY.value = event.translationY + context.translateY;
297f2fad9b1SAman Mittal  },
298f2fad9b1SAman Mittal});
299f2fad9b1SAman Mittal```
300f2fad9b1SAman Mittal
301f2fad9b1SAman MittalBoth the `onStart` and `onActive` methods accept `event` and `context` as parameters. In the `onStart` method, we'll use `context` to store the initial values of `translateX` and `translateY`. In the `onActive` callback, we'll use the `event` to get the current position of the pan gesture and `context` to get the previously stored values of `translateX` and `translateY`.
302f2fad9b1SAman Mittal
303f2fad9b1SAman MittalNext, use the `useAnimatedStyle()` hook to return an array of transforms.For the `<AnimatedView>` component, we need to set the `transform` property to the `translateX` and `translateY` values. This will change the sticker's position when the gesture is active.
304f2fad9b1SAman Mittal
305f2fad9b1SAman Mittal{/* prettier-ignore */}
306f2fad9b1SAman Mittal```jsx EmojiSticker.js
307f2fad9b1SAman Mittalconst containerStyle = useAnimatedStyle(() => {
308f2fad9b1SAman Mittal  return {
309f2fad9b1SAman Mittal    transform: [
310f2fad9b1SAman Mittal      {
311f2fad9b1SAman Mittal        translateX: translateX.value,
312f2fad9b1SAman Mittal      },
313f2fad9b1SAman Mittal      {
314f2fad9b1SAman Mittal        translateY: translateY.value,
315f2fad9b1SAman Mittal      },
316f2fad9b1SAman Mittal    ],
317f2fad9b1SAman Mittal  };
318f2fad9b1SAman Mittal});
319f2fad9b1SAman Mittal```
320f2fad9b1SAman Mittal
3213c9a6b96SBartosz KaszubowskiThen add the `containerStyle` from the above snippet on the `<AnimatedView>` component to apply the transform styles.
3223c9a6b96SBartosz KaszubowskiAlso, update the `<EmojiSticker>` component so that the `<PanGestureHandler>` component becomes the top-level component.
323f2fad9b1SAman Mittal
324f2fad9b1SAman Mittal<SnackInline
325f2fad9b1SAman Mittallabel="Handle pan gesture"
326f2fad9b1SAman MittaltemplateId="tutorial/06-gestures/App"
327f2fad9b1SAman Mittaldependencies={['expo-image-picker', '@expo/vector-icons/FontAwesome', '@expo/vector-icons', 'expo-status-bar', '@expo/vector-icons/MaterialIcons', 'react-native-gesture-handler', 'react-native-reanimated']}
328f2fad9b1SAman Mittalfiles={{
329f2fad9b1SAman Mittal  'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910',
330f2fad9b1SAman Mittal  'assets/images/emoji1.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/be9751678c0b3f9c6bf55f60de815d30',
331f2fad9b1SAman Mittal  'assets/images/emoji2.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/7c0d14b79e134d528c5e0801699d6ccf',
332f2fad9b1SAman Mittal  'assets/images/emoji3.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/d713e2de164764c2ab3db0ab4e40c577',
333f2fad9b1SAman Mittal  'assets/images/emoji4.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ac2163b98a973cb50bfb716cc4438f9a',
334f2fad9b1SAman Mittal  'assets/images/emoji5.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/9cc0e2ff664bae3af766b9750331c3ad',
335f2fad9b1SAman Mittal  'assets/images/emoji6.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ce614cf0928157b3f7daa3cb8e7bd486',
336f2fad9b1SAman Mittal  'components/ImageViewer.js': 'tutorial/02-image-picker/ImageViewer.js',
337f2fad9b1SAman Mittal  'components/Button.js': 'tutorial/03-button-options/Button.js',
338f2fad9b1SAman Mittal  'components/CircleButton.js': 'tutorial/03-button-options/CircleButton.js',
339f2fad9b1SAman Mittal  'components/IconButton.js': 'tutorial/03-button-options/IconButton.js',
340f2fad9b1SAman Mittal  'components/EmojiPicker.js': 'tutorial/04-modal/EmojiPicker.js',
341f2fad9b1SAman Mittal  'components/EmojiList.js': 'tutorial/05-emoji-list/EmojiList.js',
342f2fad9b1SAman Mittal  'components/EmojiSticker.js': 'tutorial/06-gestures/CompleteEmojiSticker.js',
343f2fad9b1SAman Mittal}}>
344f2fad9b1SAman Mittal
345f2fad9b1SAman Mittal{/* prettier-ignore */}
346f2fad9b1SAman Mittal```jsx
347f2fad9b1SAman Mittalexport default function EmojiSticker({ imageSize, stickerSource }) {
348f2fad9b1SAman Mittal  // rest of the code
349f2fad9b1SAman Mittal
350f2fad9b1SAman Mittal  return (
351f2fad9b1SAman Mittal    /* @info Wrap all components inside PanGestureHandler. */<PanGestureHandler onGestureEvent={onDrag}>/* @end */
352f2fad9b1SAman Mittal      /* @info Add containerStyle to the AnimatedView style prop. */<AnimatedView style={[containerStyle, { top: -350 }]}>/* @end */
353f2fad9b1SAman Mittal        <TapGestureHandler onGestureEvent={onDoubleTap} numberOfTaps={2}>
354f2fad9b1SAman Mittal          <AnimatedImage
355f2fad9b1SAman Mittal            source={stickerSource}
356f2fad9b1SAman Mittal            resizeMode="contain"
357f2fad9b1SAman Mittal            style={[imageStyle, { width: imageSize, height: imageSize }]}
358f2fad9b1SAman Mittal          />
359f2fad9b1SAman Mittal        </TapGestureHandler>
360f2fad9b1SAman Mittal      </AnimatedView>
361f2fad9b1SAman Mittal    /* @info */</PanGestureHandler>/* @end */
362f2fad9b1SAman Mittal  );
363f2fad9b1SAman Mittal}
364f2fad9b1SAman Mittal```
365f2fad9b1SAman Mittal
366f2fad9b1SAman Mittal</SnackInline>
367f2fad9b1SAman Mittal
368f2fad9b1SAman MittalLet's take a look at our app on iOS, Android and the web:
369f2fad9b1SAman Mittal
370f2fad9b1SAman Mittal<Video file="tutorial/pan-gesture.mp4" />
371f2fad9b1SAman Mittal
372a30150b3SAman Mittal</Step>
373a30150b3SAman Mittal
3749de0c686SAman Mittal## Next step
375f2fad9b1SAman Mittal
376f2fad9b1SAman MittalWe successfully implemented pan and tap gestures.
377f2fad9b1SAman Mittal
3789de0c686SAman Mittal<BoxLink
3799de0c686SAman Mittal  title="Take a screenshot"
3809de0c686SAman Mittal  Icon={BookOpen02Icon}
3819de0c686SAman Mittal  description="In the next chapter, we'll learn how to take a screenshot of the image and the sticker, and save it on the device's library."
3829de0c686SAman Mittal  href="/tutorial/screenshot"
3839de0c686SAman Mittal/>
384