- 探索 React Native Reanimated v2
- Shared Values (共享值)
- useAnimatedStyle Hook
- 使用新的 React Native Reanimated 概念
- 介绍 React Native Reanimated v3 的新功能
- 总结
在使用应用程序时,对象、页面、模态框和其他组件的流畅移动可以提升我们的用户体验,并鼓励用户回归。没有人愿意使用出现故障且无法正常移动的应用程序。
对于前端开发人员来说,创建动画和对象过渡可能是一种负担,因为我们通常希望专注于为我们的应用编写代码,而不是去计算在哪里放置一个对象,或者当用户在我们的应用上触发一个事件时,应该将该对象移动到哪里。
与 UI 设计师合作也可能是一项挑战,尤其是当期望不一致时——例如,当设计师期望看到他们的复杂动画被原样重现。找到一个好的工具和包来解决这个问题也不那么容易——但这正是为什么 react-native-reanimated
包被构建的原因。
react-native-reanimated
npm 包让我们能够轻松创建简单和复杂的,但流畅和互动的动画。一开始使用可能并不直观,但是随着我们在这篇文章中练习示例,我们应该能够理解如何使用这个包。
在我们继续之前,我假设你已经对 React Native 及其工作原理有所了解。此外,React Native Reanimated v3 只支持 React Native v0.64 或更高版本,因此请确保你更新或下载最新版本的 React Native 来配合这个教程。
探索 React Native Reanimated v2
如果你曾经使用过 React Native Reanimated v1,请记住 v2 引入了一些新的概念和重大变化。现在让我们来探索一些这些变化。
名称更改
Reanimated 2
中的重大变化包括一些需要记住的名称变化。例如, interpolate 方法现在是 interpolateNode
,而 Easing
方法现在是 EasingNode
。
工作集
在 React Native Reanimated v2 中,动画是一等公民。动画以 worklets 的形式用纯 JS 编写。
Worklets 是由 Reanimated Babel 插件从主 React Native 代码中选取的 JavaScript 代码片段。这些代码片段在一个单独的线程中运行,使用一个单独的 JavaScript 虚拟机上下文,并在 UI 线程上同步执行。
要创建一个工作集,你需要在函数顶部明确添加 worklet
指令。请参见下面的代码:
function someWorklet(greeting) { 'worklet'; console.log("Hey I'm running on the UI thread"); }
所有带有 worklet
指令的函数都被选中并在一个单独的 JavaScript 线程中运行。你也可以通过使用 runOnUI
来调用或执行函数,从而在 UI 线程中运行你的函数,就像这样:
function FirstWorklet(greeting) { 'worklet'; console.log("Hey I'm running on the UI thread"); } function secondFunction() { console.log("Hello World!"); } function onPress() { runOnUI(secondFunction)(); }
在上述代码中, FirstWorklet
是唯一的 worklet 函数,因此它将被 Reanimated Babel 插件选中并在一个单独的线程中运行。与此同时,其他函数将在主 JavaScript 线程中同步运行。
Shared Values (共享值)
在 Reanimated 中,共享值是在 JavaScript 端编写但用于驱动 UI 线程动画的原始值。
在探索 worklets 时,我们了解到 Reanimated 在一个单独的线程中使用一个单独的 JavaScript 虚拟机上下文运行我们的动画。当我们在主线程或 JavaScript 中创建一个值时,共享值会保持对那个可变值的引用。
由于它是可变的,我们可以对共享值进行更改,并在 UI 线程中看到引用的更改,以及我们的动画中的更改。
参见下方的代码:
import React from 'react'; import {Button, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; function App() { const sharedValue = useSharedValue(0); return ( <View> <Button title="Increase Shared Value" onPress={() => (sharedValue.value += 1)} /> </View> ); }
如我们在上面的代码中可以看到,共享值对象作为对共享数据片段的引用,可以使用它们的 .value
属性进行访问。因此,要访问和修改我们的共享数据,请使用上面看到的 .value
属性。
我们使用 useSharedValue 钩子来在我们的共享值引用中创建和存储数据。
useAnimatedStyle Hook
useAnimatedStyle
钩子使我们能够在共享值和 View
属性之间创建关联,以使用共享值来动画化我们的 View 样式。
让我们看一下下面的代码:
import { useAnimatedStyle } from 'react-native-reanimated'; const Animation = useAnimatedStyle(() => { return { ...style animation code }; });
useAnimatedStyle
钩子返回我们更新的动画样式,获取共享值,然后执行更新和样式。
就像 useEffect
钩子一样, useAnimatedStyle
钩子接受一个依赖项。当我们像上面那样省略依赖项时,只有在主体发生变化时才会调用更新。
然而,如果包含了依赖项, useAnimatedStyle
钩子会在依赖值发生变化时运行更新,如下所示:
import { useAnimatedStyle } from 'react-native-reanimated'; const Animation = useAnimatedStyle(() => { return { ...style animation code }; }, [ dependency] );
使用新的 React Native Reanimated 概念
现在我们已经探索了一些在 React Native Reanimated v2 中引入的新概念,我们将使用这些新概念在我们的应用程序中创建动画。
要使用 React Native Reanimated 库,我们首先需要安装这个库。运行下面的任一命令来安装这个包:
// yarn yarn add react-native-reanimated // npm npm i react-native-reanimated --save
接下来,进入你的 babel.config.js
文件,并按照下面所示添加插件:
module.exports = { presets: [ ... ], plugins: [ ... 'react-native-reanimated/plugin', ], };
请注意,React Native Reanimated 插件必须最后添加。
接下来,在你的 App.js 文件中,让我们创建一个带有一些标题和正文内容的简单页面:
import React from 'react'; import {View, Button, Text, StyleSheet} from 'react-native'; const App = () => { return ( <View style={styles.parent}> <Text style={styles.header}>React Native Reanimated Tutorial</Text> <View style={styles.box}> <Button title="View more" /> <View style={{marginTop: 20}}> <Text style={styles.textBody}> Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur magnam necessitatibus dolores qui sunt? Mollitia nostrum placeat esse commodi modi quaerat, et alias minima, eligendi ipsa perspiciatis, totam quod dolorum. {'n'} {'n'} Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur magnam necessitatibus dolores qui sunt? Mollitia nostrum placeat esse commodi modi quaerat, et alias minima, eligendi ipsa perspiciatis, totam quod dolorum. </Text> </View> </View> </View> ); }; export default App; const styles = StyleSheet.create({ parent: { flex: 1, paddingTop: 40, paddingHorizontal: 20, }, header: { fontSize: 24, marginBottom: 20, textAlign: 'center', }, box: { backgroundColor: '#000', borderRadius: 15, padding: 20, }, textBody: { fontSize: 20, marginBottom: 20, }, });
目标是创建一个动画下拉文本,当我们点击按钮时,它会显示出来:
要设置此组件,请将下面的内容复制到你的 ImageDescription.js
文件中:
import React from 'react'; import Animated from 'react-native-reanimated'; import {sharedElementTransition} from './SharedElements'; import {View, Text, StyleSheet, Image, Dimensions} from 'react-native'; export default function ImageDescription({route}) { const {image} = route.params; return ( <View style={styles.container}> <Text style={styles.title}>Shared Elements Transition</Text> <Animated.View style={styles.imageContainer} sharedTransitionTag="animateImageTag" sharedTransitionStyle={sharedElementTransition}> <Image source={image.img} style={styles.image} /> </Animated.View> <Text style={styles.description}>{image.description}</Text> </View> ); } const windowWidth = Dimensions.get('window').width; const styles = StyleSheet.create({ container: { flex: 1, }, title: { fontSize: 24, fontWeight: 'bold', color: '#000', marginHorizontal: 10, marginVertical: 5, }, imageContainer: { height: 500, width: windowWidth, }, image: { height: '100%', width: '100%', }, description: { fontSize: 20, color: '#000', marginHorizontal: 10, marginVertical: 5, }, });
接下来,我们将从 react-native-reanimated
中引入我们的动画视图。动画视图被包裹在你想要动画化的部分周围,在我们的情况下,是图片。 Animated.View
接受一些选项,如 sharedTransitionTag
和 sharedTransitionStyle
。
将以下内容添加到你的 SharedElements.js
文件中:
import {SharedTransition, withSpring} from 'react-native-reanimated'; const CONFIG = { mass: 1, stiffness: 100, damping: 200, }; export const sharedElementTransition = SharedTransition.custom(values => { 'worklet'; return { height: withSpring(values.currentHeight, CONFIG), width: withSpring(values.currentWidth, CONFIG), originX: withSpring(values.currentOriginX, CONFIG), originY: withSpring(values.currentOriginY, CONFIG), }; });
在上述代码中, sharedTransitionTag
让 Reanimated 能够检测到需要动画的组件。因此,具有相同 sharedTransitionTag
的组件之间会共享动画。
与此同时, sharedTransitionStyle
允许你为动画组件的 height
, width
, originY
和 originX
编写自定义样式。
总结
我们已经学习了在 React Native Reanimated v2 和 v3 中引入的变化,核心特性,以及如何使用这些核心特性。我们还看到了如何使用我们在文章中学到的概念来创建平滑的过渡和动画。
“共享元素过渡”功能仍处于实验阶段,这意味着正在开发更稳定的版本。与此同时,你可以使用它并了解其工作原理,尽管你可能还不想在生产环境中使用它。