Sitemap

Gestures in React Native Skia

5 min readSep 5, 2023

--

Crafting exceptional user interfaces is the heart of modern app development. We strive to design visually striking apps while providing a seamless user experience. React Native Skia is an ideal tool for achieving these goals.

Under the hood, React Native Skia utilizes the power of Google’s Skia library, a multiplatform 2D graphics library. For us, React Native developers, this translates to the ability to use Metal and Vulkan for graphics rendering. Moreover, the React Native Skia provides several convenient components, including shapes, filters, and more. While allowing access to shader programming in SkSL. This excellent balance of high-level and low-level APIs enhances the developer experience and contributes to excellent maintainability.

AC's temperature control

Picture this: controlling your AC’s temperature with a simple twist of a knob. This is where gestures take center stage. In this article, we’ll explore how to infuse a knob component with gesture-driven magic using React Native Skia.

Implementing Gesture Handling

For handling user's touch interactions, the useTouchHandler hook is exactly what we need. Import it from the @shopify/react-native-skia package.

This hook takes two arguments: the first argument is an object containing callbacks for single-touch events, and the second argument is an array of dependencies. As a result, it returns the touch handler that should be passed to the canvas component via the onTouch prop.

const handleTouch = useTouchHandler({
onStart: (touchInfo) => {
// when the touch starts
},
onActive: (touchInfo) => {
// ongoing touch interactions
},
onEnd: (touchInfo) => {
// when the touch ends
},
});

return <Canvas onTouch={handleTouch}></Canvas>;

The touchInfo object contains various details about the touch event, including xy coordinates, velocity, timestamp, and more.

Rotate the Knob

Back to our app, the knob should rotate based on the user's gesture. For this purpose, We can leverage the onActive callback, which provides current touch information, mainly the xy coordinates.

This code snippet calculates the angle by which is knob animated.

const angle = useValue(0);

const handleTouch = useTouchHandler({
onActive: ({ x, y }) => {
const { theta } = canvas2Polar({ x, y }, center.current);

angle.current = theta;
},
});

In order to calculate the angle, this snippet uses the canvas2Polar function, also imported from the @shopify/react-native-skia package. This function converts xy coordinates to polar coordinates. In this case, We need only the angle part of the polar coordinates.

The polar coordinate system is a two-dimensional coordinate system in which each point on a plane is determined by a distance from a reference point and an angle from a reference direction.

The result is stored in the angle variable which is initialized as a Skia mutable value by using theuseValue hook. This hook creates an animation value value within a Skia world.

Providing the Angle to the Parent Component

The knob is designed to provide onRotate prop to the parent component with an angle between the start and the end of the gesture.

The angle between the start and end of the gesture

To do so, we need to calculate the start angle value similarly to the angle.

const startAngle = useValue(0);
const angle = useValue(0);

const handleTouch = useTouchHandler({
onStart: ({ x, y }) => {
const { theta } = canvas2Polar({ x, y }, center.current);

startAngle.current = theta;
},
onActive: ({ x, y }) => {
const { theta } = canvas2Polar({ x, y }, center.current);

angle.current = theta;
},
});

By subtracting these two values, we get the desired value.

useValueEffect(angle, (currentAngle) => {
const radians = startAngle.current - currentAngle;
const degrees = toDegrees(radians); // convert radians to degrees

onRotate(degrees);
});

This snippet demonstrates the usage of the useValueEffect hook, which takes exactly two arguments: the first Skia value to be watched and the second argument is a callback function that executes when the value changes.

The callback function is implemented to calculate the angle by subtracting the startAngle from the angle value. Although we’ve been working with radians so far, it’s more convenient to provide degrees to the onRotate callback. React Native Skia provides a toDegrees utility, which converts the radians into degrees.

Skia Component with Gesture Handling

Finally, there is the complete Knob component with all the bits and pieces mentioned in this article.

import {
Canvas,
canvas2Polar,
toDegrees,
useComputedValue,
useTouchHandler,
useValue,
useValueEffect,
vec,
} from '@shopify/react-native-skia';

import { Border } from './parts/Border';
import { Marks } from './parts/Marks';

interface IKnob {
onRotate: (angle: number) => void;
}

export const Knob = ({ onRotate }: IKnob) => {
const size = useValue({ width: 0, height: 0 });
const center = useComputedValue(
() => vec(size.current.width / 2, size.current.height / 2),
[size],
);
const angle = useValue(0);
const startAngle = useValue(0);

const handleTouch = useTouchHandler({
onStart: ({ x, y }) => {
const { theta } = canvas2Polar({ x, y }, center.current);

startAngle.current = theta;
},
onActive: ({ x, y }) => {
const { theta } = canvas2Polar({ x, y }, center.current);

angle.current = theta;
},
});

useValueEffect(angle, (currentAngle) => onRotate(toDegrees(startAngle.current - currentAngle)));

return (
<Canvas onTouch={handleTouch} onSize={size}>
<Border size={size} center={center} />
<Marks size={size} rotation={angle} />
</Canvas>
);
};

The snippet below shows how this component is used.

import { useCallback, useState } from 'react';
import { Text, View } from 'react-native';

import { Knob } from './parts/Knob';

export const TemperatureControl = () => {
const [temperature, setTemperature] = useState(0);

const handleRotate = useCallback((angle: number) => {
// execute logic on knob rotation
}, []);

return (
<View>
<Text>Temperature: {temperature.toFixed(1)} °C</Text>
<Knob onRotate={handleRotate} />
</View>
);
};

Conclusion

And that’s it! In this article, we saw the usage of the useTouchHandler hook, thanks to which we were able to work with touch info. We also saw some more useful functions like canvas2Polar, which helped us get the angle from the xy coordinates. Finally, we were able to implement the onRotate callback thanks to useValueEffect, which reacts to value change on Skia mutable values.

--

--