The Basics of React Native Gestures

Everything around you is interactive. You can touch, manipulate every object you have using buttons, sliders and many more.

In React Native, we use touch to declare gestures and it is crucial to know how to use them wisely. In today’s chapter, let’s learn about the basics of gestures.

This was first published as a video for the How To Animated series. If you learn better through audio/image, I highly recommend it.

This article is part of a series. If you are unfamiliar with the basics of animations (Animated.Value, Animated.View, interpolate), the first chapter should help you to understand what’s next.

Responders

A very important concept for React Native gestures is called a responder. The way it works is simple: imagine that your application has one guy in charge of handling gestures.

Whenever you press or move something, you need to ask him the right to proceed with the gesture. Another rule is that only one gesture at the time can get his attention.

In our application, we’ll call this guy the “gesture responder” and fortunately, we have an easy way to talk to him.

If a button for example wants to be touchable, it needs to let the gesture responder know that if a user touches it, no matter what other movement they do, this button only should be pressed.

In other words, whenever you interact with an element or a view in your app, you need to let the gesture responder know which element should become interactive and how.

This layer is usually abstracted for you since we use components that have all the logic we need for them to work: button, touchables and pressable.

What might be new to you this time is that you’ll be in charge of telling the gesture responder when and how you want it to make a view interactive.

Responder Lifecycle

As I said, there is a simple way for you to contact the gesture responder. If you have already heard about Pan Responders, let me tell you that there is even a simpler way of doing it.

Every React Native View can become interactive the way you want it to be. All you need, is to let the gesture responder know how it should deal with a gesture happening inside that view.

To understand how it works, we need to know what is a gesture. It always starts with a touch, somewhere on the screen. Then you move it around, and you eventually release the touch.

These three steps are what define any gesture. A start event, a series of move events, and a final one, the release event.

Every view can keep track of this, which means that each view can ask the gesture responder to act in a certain way when interacted with. To do so, there is a bunch of properties every View component comes with.

The first two, onStartShouldSetResponder and onMoveShouldSetResponder define if a View should be talking to the responder, and then become interactive. If these properties return true, then when touching this View, we’ll get all the touch events within it:

  • onStartShouldSetResponder: Does this view want to become responder on the start of a touch?
onStartShouldSetResponder: (event) => true | false
  • onMoveShouldSetResponser: Called for every touch move on the View when it is not the responder. Does this view want to “claim” touch responsiveness?
onMoveShouldSetResponder: (event) => true | false

The next two props aren’t usually useful. They’re just functions called whenever a View has been granted or rejected by the responder. But just so you know, they exist:

  • onResponderGrant: The View is now responding for touch events.
  • onResponderReject: Something else is the responder right now.

What you really want to know about is onResponderMove and onResponderRelease. When a user’s touch is moving on the screen, every move event will be fired through onResponderMove. When it’s over, it’s onResponderRelease.

Now picture this: if you want to move an image around, and when you stop touching the screen it brings this image back in the middle, you could do so using these callbacks. Calling onResponderMove when the image is moved, then animate it back to the center when onResponderRelease is called:

  • onResponderMove: The user is moving their finger.
onResponderMove: (event) => { ... }
  • onResponderRelease: Fired at the end of the touch.
onResponderRelease: (event) => { ... }

Alright, you know all the theory, it’s time for practice.

Practice

Here’s what I want us to make today: first, I want to move a cursor around. Just a small circle that will follow my touch. Then, I want to take this cursor back to its middle position when I release the touch.

Now for this, I will need two things: a view that will listen to my gesture, and another one to show the cursor. Because the gesture will happen on both the x and y axes, we’ll create an XY animated value to store the current touch position:

import React, { useRef } from 'react';
import {
Animated,
useWindowDimensions,
View,
} from 'react-native';
const CURSOR_SIDE_SIZE = 20;
const CURSOR_HALF_SIDE_SIZE = CURSOR_SIDE_SIZE / 2;
export default (props) => {
const touch = useRef(
new Animated.ValueXY({ x: 0, y: 0 })
).current;

const dimensions = useWindowDimensions();

return (
<View style={{ flex: 1 }}>
<View
style={{
position: 'absolute',
left:
dimensions.width / 2 -
CURSOR_HALF_SIDE_SIZE,
top:
dimensions.height / 2 -
CURSOR_HALF_SIDE_SIZE,
height: CURSOR_SIDE_SIZE,
width: CURSOR_SIDE_SIZE,
borderRadius: CURSOR_HALF_SIDE_SIZE,
backgroundColor: 'orange',
}}
/>
</View>
);
};

This is something I haven’t talked much about yet. The idea is that when we’ll move the touch around the screen, it should update this XY animated value. When the value is updated, it should also be reflected in the cursor view position.

Also, before we continue I want to say that if you don’t get this the first time, it’s normal. The way we handle gestures follows a method that might be new to you, but the good thing is that it is often the same pattern we put in place. Don’t hesitate to come back to this chapter to make sure it’s clear on your mind.

Alright, back to the example, we have all the components we need to get started. What we’ll do first, is to set the parent view as the one responsible for the touch events.

If we set onStartShouldSetResponder to true, we’re letting our gesture responder guy know that this view should get all the move events happening on this part of the screen. Even if I move my hand on the cursor view, which is a child of the parent, it shouldn’t be affected since only the parent asked to be the touch responder. Remember, we need to ask for it, otherwise it won’t be interactive by default.

<View
onStartShouldSetResponder={() => true}

So after asking for the view to become the touch responder on start events, we actually need to do something whenever the touch moves. In our case, we want to update our animated value to store the current touch position:

<View
onStartShouldSetResponder={() => true}
onResponderMove={(event) => {

}}

If we look at the onResponderMove callback event, it contains a few interesting information on the current touch:

  • pageX: The X position of the touch, relative to the root element.
  • pageY: The Y position of the touch, relative to the root element.
  • locationX: The X position of the touch, relative to the element.
  • locationY: The Y position of the touch, relative to the element.

The ones that are useful for us are locationX and locationY which both correspond to our current finger position on both axes and relative to our View component.

We’ll call setValue on our animated value and set the new position using the event values:

<View
onStartShouldSetResponder={() => true}
onResponderMove={(event) => {
touch.setValue({
x: event.nativeEvent.locationX,
y: event.nativeEvent.locationY,
});
}}

Now that we listen to the touch event when it moves, that we have the current touch position, we can move the cursor View around based on the animated value.

To do so, you know the drill, we turn it into an animated view, and set the left property to the x cursor position, and top to y:

<Animated.View
style={{
position: 'absolute',
left: touch.x,
top: touch.y,
height: CURSOR_SIDE_SIZE,
width: CURSOR_SIDE_SIZE,
borderRadius: CURSOR_HALF_SIDE_SIZE,
backgroundColor: 'orange',
}}
/>

You might have noticed something which is that the cursor won’t be exactly positioned where I touched, because if I set the view to be at the exact same touch position, it will draw the circle below the touch.

We can center it by subtracting half of the circle size to the touch position on both axes. Remember that you cannot manipulate animated values like this…

left: touch.x - CURSOR_HALF_SIDE_SIZE,
top: touch.y - CURSOR_HALF_SIDE_SIZE,

…but you need to use Animated operator methods that do exactly what you think they will. In this case, we need to subtract the CURSOR_HALF_SIDE_SIZE constant so let’s use Animated.substract:

left: Animated.substract(touch.x, CURSOR_HALF_SIDE_SIZE),
top: Animated.substract(touch.y, CURSOR_HALF_SIDE_SIZE),

Great, we can now add the last part which is bringing the cursor back to its middle position after we release our touch.

We can use onResponderRelease for that, which should call a spring animation on the animated value. The x and y values correspond to the middle point of the screen, minus half the the cursor side:

<Animated.View
onResponderRelease={() => {
Animated.spring(touch, {
toValue: {
x:
dimensions.width / 2 -
CURSOR_HALF_SIDE_SIZE,
y:
dimensions.height / 2 -
CURSOR_HALF_SIDE_SIZE,
},
// left/top are not supported
useNativeDriver: false,
}).start();
}}

Simple, isn’t it?

Remember that this is just an example of what you can do with bare properties on Views, but there is a lot more to explore using the other properties from the touch events.

You can add all sorts of logic, such as blocking a gesture past a certain point, or snapping the values around given thresholds.

This is not something you’ll learn from a book, you’ll need to actually do it to learn. Be brave and give it a shot!

Recap

If there are three things to remember today, here they are:

  • A gesture responder in React Native is in charge of gestures and needs to be talked to for an element to become interactive.
  • Every View can use its properties to tell the responder when and how it should turn interactive ( onMoveShouldSetResponder, onResponderMove…).
  • These properties are useful to define side-effects based on a gesture, namely when it starts, moves and ends.

Alright, that’s it for now. In the next chapter, we’ll keep learning about gestures following what we’ve learned today, using a Pan Responder this time.

New chapters will be released on a weekly-basis. Remember to follow me if you don’t want to miss any.

I often find myself reading too many articles on the internet

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store