React Native

Introduction to manual gestures and touch events

Jakub PiaseckiDec 22, 20215 min read

One of the most requested features of Gesture Handler has been the ability to directly manipulate the state of gestures. With the newly released Gesture Handler 2.0, we introduced the new “manual gesture” API which fills that demand (read the announcement post here to learn about other changes we made in Gesture Handler 2.0), thus opening up a lot of new possibilities for gesture-based interactions. In this article we explore this new feature while building a simple React Native application.

How does it work?

At the core of this functionality are newly introduced touch events. They are sent every time a pointer gets updated while the gesture is active or hasn’t yet recognized nor failed, carrying information about all fingers tracked by the gesture, as well as those that have been changed since the last event.

There are four types of touch events:

  • TOUCHES_DOWN — sent every time a finger is placed on the screen and is picked up by the gesture
  • TOUCHES_UP — sent every time a finger tracked by the gesture has been lifted from the screen
  • TOUCHES_MOVE — sent every time a finger tracked by the gesture is moved
  • TOUCHES_CANCELLED — send when some (usually all) pointers tracked by the gesture have been cancelled, for example when the gesture fails.

These events can be handled in the same way as other events, each one of them has associated callback that gets called upon receiving the event.

Let’s see it in action

To demonstrate this new feature we will build an app that displays a full-screen pane with a number of discs. Each disc can be panned and pinched in order to change its position and size. As gesture-handler works best with react-native-reanimated, we will use that library to power gesture-based interactions. Here is how this app would look.

Touch events example

Making gestures work properly in such an app may be quite tricky — if we attach gesture recognizers to each individual disc, small discs can be really difficult to hit (to make it larger you’ll need to fit two fingers within the disc area). We can’t really just make the hit area larger for each disc, as it would obstruct the hit area of other nearby discs. It turns out that this can’t really be solved using the set of declarative gesture properties from the old version of gesture handler, but the new manual gesture feature from Gesture Handler 2.0 makes such a scenario relatively straightforward to implement. One way this can be solved is by making each disc hit area to be the full screen, and allow for certain gestures to activate based on proximity, e.g., pan would only recognize when we are within the disc bounds, and pinch would activate if the central point between the fingers is within the disc bounds.

We can begin by creating a Disc component that will render a ball with specified size and color, and its children wrapped within a view filling the entire screen. This will allow us to capture every touch, even ones that occur outside the disc. We use reanimated’s shared values to make disc offset (translation) and scale animatable.

Below, we show the component responsible for rendering discs with different sizes. Discs need to be nested to allow for events to be delivered to all of them instead of just the top-most one.

In order to add interaction to the discs we need to add shared values to store state of the transformations:

Now we can create gesture configurations for the disc. We want to be able to move and scale it, so we need to create Pan and Pinch gesture objects. In both cases we want to use and update the relevant saved value to avoid the disc jumping back to its original position or size.

We also have to wrap rendered views with GestureDetector and pass gesture configs to it. In this case we will use Simultaneous to compose them because we want to allow both of them to be activated at the same time.

If we try it now we will see that only the smallest disc is responding to the touches and it doesn’t matter where we touch the screen. That’s because the view containing it is filling the entire screen and the pan gesture doesn’t have any activation constraints, so it activates every time you move a finger cancelling other gestures (the same goes for pinch). We would like the pinch to be able to activate only when its focal point is inside the disc, and pan only when the finger is placed inside the disc or the central point is inside the disc. That’s where touch events come into play as they allow us to check that easily. First we will make a helper function checking if a point is inside a view:

Next, we can add touch event callbacks. In the case of a pinch, we want it to activate when the central point is inside the disc and fail otherwise.

For this example we want the discs to be able to be moved and scaled at the same time, because of that we need to handle situations where fingers are placed outside the disc — it should be possible to move it when just the central point is inside of it. The simplest way to accomplish this is to use the new manualActivation feature that will prevent the pan gesture from activating by itself. This way we can utilize onTouchesMove to check whether the middle point between the fingers (in case of one finger that would be its position) is inside the disc and activate the pan if it is.

That’s it! Gesture interactions work as expected, although it lacks some sort of visual feedback. Adding it is very straightforward though, so we won’t describe it here but it’s included in the complete code of the example here if you want to check it out.

In this article we used touch events to manually control the state of Pan and Pinch gesture handlers. You can do the same with all built-in handlers from gesture-handler library, but they also allow you to go beyond what the library provides and build a completely custom handler using touch events in conjunction with the newly introduced Manual gesture (check out Manual gesture documentation website for more information).

The work on Gesture Handler 2 and the new manual gesture feature has been sponsored by Shopify 🙌.