在本教程中,了解如何从 React Native Gesture Handler 和 Reanimated 库实现手势。
手势是在应用中提供直观用户体验的好方法。React Native 手势处理程序库 提供了可以处理手势的内置原生组件。它使用平台的原生触摸处理系统来识别平移、点击、旋转和其他手势。
¥Gestures are a great way to provide an intuitive user experience in an app. The React Native Gesture Handler library provides built-in native components that can handle gestures. It uses the platform's native touch handling system to recognize pan, tap, rotation, and other gestures.
在本章中,我们将使用 React Native Gesture Handler 库添加两种不同的手势:
¥In this chapter, we are going to add two different gestures using the React Native Gesture Handler library:
双击可缩放表情符号贴纸的大小。
¥Double tap to scale the size of the emoji sticker.
平移以在屏幕上移动表情符号贴纸,以便用户可以将贴纸放置在图片上的任何位置。
¥Pan to move the emoji sticker around the screen so that the user can place the sticker anywhere on the image.
1
¥Install and configure libraries
React Native Gesture Handler 库提供了一种与原生平台的手势响应系统交互的方法。为了在手势状态之间设置动画,我们将使用 复活的库。
¥The React Native Gesture Handler library provides a way to interact with the native platform's gesture response system. To animate between gesture states, we will use the Reanimated library.
要安装它们,请按 Ctrl + c 停止开发服务器,然后在终端中运行以下命令:
¥To install them, stop the development server by pressing Ctrl + c and run the following command in the terminal:
-
npx expo install react-native-gesture-handler react-native-reanimated
现在,再次启动开发服务器:
¥Now, start the development server again:
-
npx expo start
为了让手势交互在应用中工作,我们将从 react-native-gesture-handler
渲染 <GestureHandlerRootView>
以封装应用的顶层组件(也称为 "根组件")。
¥To 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").
为此,请将 App.js 中的根级别 <View>
组件替换为 <GestureHandlerRootView>
。
¥To accomplish this, replace the root level <View>
component in the App.js with <GestureHandlerRootView>
.
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function App() {
return (
<GestureHandlerRootView style={styles.container}>
{/* ...rest of the code remains */}
</GestureHandlerRootView>
)
}
2
¥Use animated components
打开组件目录中的 EmojiSticker.js 文件。在其中,从 react-native-reanimated
库导入 Animated
以使用动画组件。
¥Open the EmojiSticker.js file in the components directory. Inside it, import Animated
from the react-native-reanimated
library to use animated components.
import Animated from 'react-native-reanimated';
Animated
组件查看组件的 style
属性。它还确定要设置动画的值并应用更新来创建动画。
¥The Animated
component looks at the style
prop of the component. It also determines which values to animate and applies updates to create an animation.
Reanimated 导出动画组件,例如 <Animated.View>
、<Animated.Text>
或 <Animated.ScrollView>
。我们将向 <Animated.Image>
组件应用动画,以使双击手势发挥作用。
¥Reanimated exports animated components such as <Animated.View>
, <Animated.Text>
, or <Animated.ScrollView>
. We will apply animations to the <Animated.Image>
component to make a double tap gesture work.
将 <Image>
组件替换为 <Animated.Image>
。
¥Replace the <Image>
component with <Animated.Image>
.
export default function EmojiSticker({ imageSize, stickerSource }) {
return (
<View style={{ top: -350 }}>
<Animated.Image
source={stickerSource}
resizeMode="contain"
style={{ width: imageSize, height: imageSize }}
/>
</View>
);
}
有关动画组件 API 的完整参考,请参阅 React Native 复活 文档。
¥For a complete reference on the animated component API, see React Native Reanimated documentation.
3
¥Add a tap gesture
React Native Gesture Handler 允许我们在检测到触摸输入时添加行为,例如双击事件。
¥React Native Gesture Handler allows us to add behavior when it detects touch input, like a double tap event.
在 EmojiSticker.js 文件中,从 react-native-gesture-handler
导入 Gesture
和 GestureDetector
,从 react-native-reanimated
导入下面的钩子。当识别点击手势时,这些钩子将为贴纸的 <Animated.Image>
组件上的样式设置动画。
¥In the EmojiSticker.js file, import Gesture
and GestureDetector
from react-native-gesture-handler
and the hooks below from react-native-reanimated
.
These hooks will animate the style on the <Animated.Image>
component for the sticker when the tap gesture is recognized.
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
在 <EmojiSticker>
组件内,使用 useSharedValue()
钩子创建一个名为 scaleImage
的引用。它将把 imageSize
的值作为它的初始值。
¥Inside the <EmojiSticker>
component, create a reference called scaleImage
using the useSharedValue()
hook. It will take the value of imageSize
as its initial value.
const scaleImage = useSharedValue(imageSize);
使用 useSharedValue()
钩子创建共享值有很多优点。它有助于改变一段数据并允许根据当前值运行动画。可以使用 .value
属性访问和修改共享值。它将缩放 scaleImage
的初始值,以便当用户双击贴纸时,它会缩放到原始大小的两倍。为此,我们将创建一个对象并将其命名为 doubleTap
。该对象将使用 Gesture.Tap()
在缩放贴纸图片时为过渡设置动画。numberOfTaps
方法确定需要多少次抽头。
¥Creating 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.
A 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,
it scales to twice its original size. To do this, we will create an object and call it doubleTap
. This object will use the Gesture.Tap()
to animate the transition while scaling the sticker image. The numberOfTaps
method determines how many taps are required.
在 <EmojiSticker>
组件中创建以下对象:
¥Create the following object in the <EmojiSticker>
component:
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
if (scaleImage.value !== imageSize * 2) {
scaleImage.value = scaleImage.value * 2;
}
});
为了使过渡动画化,我们使用基于弹簧的动画。这会让它感觉充满活力,因为它是基于真实世界的弹簧物理原理。我们将使用 react-native-reanimated
提供的 withSpring()
功能。
¥To 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.
We will use the withSpring()
function provided by react-native-reanimated
.
react-native-reanimated
中的 useAnimatedStyle()
钩子用于创建将应用于贴纸图片的样式对象。当动画发生时,它将使用共享值更新样式。在本例中,我们缩放图片的大小,这是通过操纵 width
和 height
属性来完成的。这些属性的初始值设置为 imageSize
。
¥The useAnimatedStyle()
hook from react-native-reanimated
is used to create a style object that will be applied to the sticker image.
It will update styles using the shared values when the animation happens. In this case, we are scaling the size of the image,
which is done by manipulating the width
and height
properties. The initial values of these properties are set to imageSize
.
创建一个 imageStyle
变量并将其添加到 EmojiSticker
组件中:
¥Create an imageStyle
variable and add it to the EmojiSticker
component:
const imageStyle = useAnimatedStyle(() => {
return {
width: withSpring(scaleImage.value),
height: withSpring(scaleImage.value),
};
});
接下来,用 <GestureDetector>
组件封装在屏幕上显示贴纸的 <Animated.Image>
组件,并修改 Animated.Image
上的 style
属性以传递 imageStyle
。
¥Next, wrap the <Animated.Image>
component that displays the sticker on the screen with the <GestureDetector>
component and modify the style
prop on the Animated.Image
to pass the imageStyle
.
export default function EmojiSticker({ imageSize, stickerSource }) {
// ...rest of the code remains same
return (
<View style={{ top: -350 }}>
<GestureDetector gesture={doubleTap}>/* @end */
<Animated.Image
source={stickerSource}
resizeMode="contain"
style=
{[imageStyle, { width: imageSize, height: imageSize }]}
/>
/* @info */</GestureDetector>
</View>
);
}
在上面的代码片段中,gesture
属性采用 doubleTap
的值,当用户双击贴纸图片时会触发手势。
¥In the above snippet, the gesture
prop takes the value of the doubleTap
which triggers a gesture when a user double-taps the sticker image.
让我们看看我们在 Android、iOS 和 Web 上的应用:
¥Let's take a look at our app on Android, iOS and the web:
有关点击手势 API 的完整参考,请参阅 React Native 手势处理程序 文档。
¥For a complete reference on the tap gesture API, refer to the React Native Gesture Handler documentation.
4
¥Add a pan gesture
平移手势允许识别拖动手势并跟踪其移动。我们将使用它将贴纸拖过图片。
¥A pan gesture allows recognizing a dragging gesture and tracking its movement. We will use it to drag the sticker across the image.
在 EmojiSticker.js 中,将 <View>
组件替换为 <Animated.View>
组件。
¥In the EmojiSticker.js, replace the <View>
component with the <Animated.View>
component.
export default function EmojiSticker({ imageSize, stickerSource }) {
// ...rest of the code remains same
return (
<Animated.View style={{ top: -350 }}>
<GestureDetector gesture={doubleTap}>
{/* ...rest of the code remains same */}
</GestureDetector>
</Animated.View>
);
}
现在,创建两个新的共享值:translateX
和 translateY
。
¥Now, create two new shared values: translateX
and translateY
.
export default function EmojiSticker({ imageSize, stickerSource }) {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
// ...rest of the code remains same
}
这些平移值将在屏幕上移动贴纸。由于贴纸沿两个轴移动,因此我们需要分别跟踪 X 和 Y 值。
¥These 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.
在 useSharedValue()
钩子中,我们将两个翻译变量设置为初始位置 0
。这意味着贴纸最初放置的位置被视为起点。该值设置手势开始时贴纸的初始位置。
¥In the useSharedValue()
hooks, we have set both translation variables to have an initial position of 0
.
This 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.
在上一步中,我们为链接到 Gesture.Tap()
方法的点击手势触发了 onStart()
回调。同样,对于平移手势,我们必须指定一个 onChange()
回调,该回调在手势处于活动状态且正在移动时运行。
¥In the previous step, we triggered the onStart()
callback for the tap gesture chained to the Gesture.Tap()
method.
Similarly, for the pan gesture, we have to specify an onChange()
callback which runs when the gesture is active and is moving.
创建一个 drag
对象来处理平移手势。
¥Create a drag
object to handle the pan gesture.
const drag = Gesture.Pan()
.onChange((event) => {
translateX.value += event.changeX;
translateY.value += event.changeY;
});
onChange()
回调接受 event
作为参数。changeX
和 changeY
属性保存自上次事件以来位置的变化。它们用于更新存储在 translateX
和 translateY
中的值。
¥The onChange()
callback accepts event
as a parameter. changeX
and changeY
properties hold the change in position since the last event. They are used to update the values stored in translateX
and translateY
.
接下来,使用 useAnimatedStyle()
钩子返回转换数组。对于 <Animated.View>
组件,我们需要将 transform
属性设置为 translateX
和 translateY
值。当手势处于活动状态时,这将更改贴纸的位置。
¥Next, use the useAnimatedStyle()
hook to return an array of transforms. For the <Animated.View>
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.
const containerStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
{
translateY: translateY.value,
},
],
};
});
然后将上述代码片段中的 containerStyle
添加到 <Animated.View>
组件上以应用变换样式。另外,更新 <EmojiSticker>
组件,使 <GestureDetector>
组件成为顶层组件。
¥Then add the containerStyle
from the above snippet on the <Animated.View>
component to apply the transform styles.
Also, update the <EmojiSticker>
component so that the <GestureDetector>
component becomes the top-level component.
export default function EmojiSticker({ imageSize, stickerSource }) {
// rest of the code
return (
<GestureDetector gesture={drag}>/* @end */
<Animated.View style=
{[containerStyle, { top: -350 }]}
>
<GestureDetector gesture={doubleTap}>
<Animated.Image
source={stickerSource}
resizeMode="contain"
style={[imageStyle, { width: imageSize, height: imageSize }]}
/>
</GestureDetector>
</Animated.View>
</GestureDetector>
);
}
让我们看看我们在 Android、iOS 和 Web 上的应用:
¥Let's take a look at our app on Android, iOS and the web:
¥Next step
我们成功实现了平移和点击手势。
¥We successfully implemented pan and tap gestures.
在下一章中,我们将学习如何截取图片和贴纸的屏幕截图,并将其保存在设备的库中。