How to Use the Pinch to Zoom Gesture in React Native Apps

The open-source library react-native-gesture-handler is a great way to add gestures to cross-platform React Native apps. Two of the main reasons I find the library useful are because it uses native support to handle gestures, and it performs better on each native platform than React Native’s built-in touch system Gesture Responder system.

In this tutorial, let’s explore this library by creating a small demo that allows the user to use two fingers to pinch to zoom in on a piece of media content.

For the media content in this tutorial, I’m going to use a placeholder image. This pinch gesture is achievable using PinchGestureHandler from the library itself. This handler tracks the distance between two fingers and uses that information to scale or zoom in on the content. It gets activated when the fingers are placed on the screen and when their position changes.

Table of contents

  • Requirements
  • Setting up react-native-gesture-handler
  • Set up App component to display an image
  • Using dynamic Image component with Animated API
  • Adding Animated event and state change handler
  • Conclusion

Requirements

  • Node.js >= 10.x.xversion installed
  • watchman
  • react-native-cli

Do note that I’m going to use an iOS simulator for this tutorial.

Setting up react-native-gesture-handler

To get started, create a bare React Native project using the react-native CLI by running the below commands from a terminal window:

react-native init pinchZoomGesture

# after the project directory is created
# and dependencies are installed
cd pinchZoomGesture

The react-native-gesture-handler supports both react-native CLI projects and Expo projects. To install it, execute the below command:

yarn add react-native-gesture-handler

For the current demo, since you’re using the react-native CLI, only Android users have to add the following configuration to the MainActivity.java file:

package com.swipegesturesdemo;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;

public class MainActivity extends ReactActivity {

 /**
 * Returns the name of the main component registered from JavaScript. This is used to schedule
 * rendering of the component.
 */
 @Override
 protected String getMainComponentName() {
 return "swipeGesturesDemo";
 }

 @Override
 protected ReactActivityDelegate createReactActivityDelegate() {
 return new ReactActivityDelegate(this, getMainComponentName()) {
 @Override
 protected ReactRootView createRootView() {
 return new RNGestureHandlerEnabledRootView(MainActivity.this);
 }
 };
 }
}

For iOS users, navigate inside the ios/ directory from the terminal and run pod install.

Everything is set up—all you have to do is run the build command again, such as for iOS: react-native run-ios; and for Android: react-native run-android.

Set up the App component to display an image

In this section, let’s quickly set up the App component to display out placeholder image. You can use any image as a placeholder. Here’s the snippet for the App.js file to get started:

import React from 'react'
import { Image, View, Dimensions } from 'react-native'

const { width } = Dimensions.get('window')

const App = () => {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Image
        source={{
          uri: 'https://miro.medium.com/max/1080/1*7SYuZvH2pZnM0H79V4ttPg.jpeg'
        }}
        style={{
          width: width,
          height: 300
        }}
        resizeMode='contain'
      />
    </View>
  )
}

export default App

It uses the width of the device’s screen to calculate the width of the image using Dimensions from react-native. To run this demo for the first time, build the app for the platform you’re using:

  • for iOS, run: react-native run-ios
  • for Android, run: react-native run-android

Here’s the output when the app runs for the first time:

Using a dynamic Image component with the Animated API

Animated.Image is going to serve the purpose of displaying an image, as well as performing scale animations.

The Animated API uses declarative relationships between input and output values. For single values, we’ll use Animated.Value(). It’s required since it’s going to be a style property initially.

Start by importing Animated from react-native and replace the Image with Animated.Image.

import { View, Dimensions, Animated } from 'react-native'

// in return statement
return (
  <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
    <Animated.Image
      source={{
        uri: 'https://miro.medium.com/max/1080/1*7SYuZvH2pZnM0H79V4ttPg.jpeg'
      }}
      style={{
        width: width,
        height: 300,
        transform: [{ scale: 1 }]
      }}
      resizeMode='contain'
    />
  </View>
)

Also, by mentioning the value of the scale to one, it’s going to display the image as usual.

Now wrap the Animated.Image with the PinchGestureHandler. This wrapper component is going to have two props.

return (
  <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
    <PinchGestureHandler
      onGestureEvent={this.onZoomEvent}
      onHandlerStateChange={this.onZoomStateChange}>
      <Animated.Image
        source={{
          uri: 'https://miro.medium.com/max/1080/1*7SYuZvH2pZnM0H79V4ttPg.jpeg'
        }}
        style={{
          width: width,
          height: 300,
          transform: [{ scale: this.scale }]
        }}
        resizeMode='contain'
      />
    </PinchGestureHandler>
  </View>
)

Adding an Animated event and state change handler

Let’s define the onZoomEvent first before the return statement. This event is going to be an Animated event. This way gestures can directly map to animated values. The animated value to be used here is scale.

Passing useNativeDriver with its boolean value set to true allows the animations to happen on the native thread instead of the JavaScript thread. This helps with performance.

scale = new Animated.Value(1)

onZoomEvent = Animated.event(
  [
    {
      nativeEvent: { scale: this.scale }
    }
  ],
  {
    useNativeDriver: true
  }
)

Now define the handler method onZoomStateChange that handles the state change when the gesture is over. Each gesture handler has assigned a state that changes when a new touch event occurs.

There are different possible states for every handler, but for the current gesture handler, ACTIVE is used to check whether the event is still active or not. To access these states, we need to import the object from the library itself.

The Animated.spring on the scale property has toValue set to 1, which is the initial scale value when the animation is done.

onZoomStateChange = event => {
  if (event.nativeEvent.oldState === State.ACTIVE) {
    Animated.spring(this.scale, {
      toValue: 1,
      useNativeDriver: true
    }).start()
  }
}

This completes all the configuration and handlers required. Check out the demo below to see it in action:

Conclusion

This completes the tutorial on how to use one of the gestures from the react-native-gesture-handler library. I recommend working through the official documentation and methods and trying out some other gestures as well.

You can find the complete code at this GitHub repo.

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square