在 React Native 开发中,动画是提升用户体验的重要手段之一。然而,传统的动画实现方式往往存在性能瓶颈,尤其是在处理复杂手势动画时。React Native Reanimated 作为一款强大的动画库,通过在 UI 线程上运行动画逻辑,解决了这一问题。本文将带你从基础到实战,全面了解 Reanimated 的核心概念和应用场景。

什么是 Reanimated?

Reanimated 是一个 React Native 动画库,允许开发者在 UI 线程上运行动画逻辑,从而实现流畅且高性能的动画效果。与传统的 React Native Animated API 不同,Reanimated 通过 Worklets(工作线程)实现了与 UI 线程的直接通信,避免了 JavaScript 线程与 UI 线程之间的通信延迟。

创建项目

我们可以通过 expo cli 直接拉取配置好的模板


sql

代码解读

复制代码

yarn create expo-app learn-animation -e with-reanimated

参数 -e with-reanimated 指定了使用带有 Reanimated 的模板。更多模板可以参考 expo/examples

创建第一个动画

使用 Animated 组件

第一步使用 Animated 组件:


javascript

代码解读

复制代码

import Animated from 'react-native-reanimated'; ​ export default function App() {  return (    <Animated.View      style={{        width: 100,        height: 100,        backgroundColor: 'violet',     }}    /> ); }

Animated组件将RN内置组件包裹了一层,例如:view, ScrollView, FlatList 等。

我们也可以自定义创建动画组件通过 createAnimatedComponet

创建共享值

共享值用于操控动画,相当于在UI线程和Js线程共享的一个state。可以通过useSharedValue 来创建。

和 React Hook 中 useState 中使用方法一样。

现在我们使用useSharedValue来创建View的宽度:


arduino

代码解读

复制代码

import Animated, { useSharedValue } from 'react-native-reanimated'; ​ export default function App() {  const width = useSharedValue(100); ​  return (    <Animated.View      style={{        width,        height: 100,        backgroundColor: 'violet',     }}    /> ); }

使用共享值

我们加一个按钮来控制 View 的宽度:


javascript

代码解读

复制代码

import { Button, View } from 'react-native'; import Animated, { useSharedValue } from 'react-native-reanimated'; ​ export default function App() {  const width = useSharedValue(100); ​  const handlePress = () => {    width.value = width.value + 50; }; ​  return (    <View style={{ flex: 1, alignItems: 'center' }}>      <Animated.View        style={{          width,          height: 100,          backgroundColor: 'violet',       }}      />      <Button onPress={handlePress} title="Click me" />    </View> ); }

注意这里使用共享值需要使用width.value, 这里 .value 是关键。

现在我们点击按钮发现View会变宽,但是没有动画效果。

我们试着把 width.value = withSpring(width.value + 50); 替换掉。这里withSpring 给我们的共享值变化提供了过度效果。像这样的函数我们下面还会学到很多,如 withTiming

宽度增加动画.gif

style和props中的动画

style动画

我们上面是在内联样式中使用我们的共享值,这种方法比较便捷,但有一定的局限性,比如:

<Animated.View style={{ width: width * 5 }} /> // 不会生效

不能在内联样式中直接访问共享值,这样我们在处理复杂动画逻辑就不能使用这个方法。

下面介绍一个例子来解决:


javascript

代码解读

复制代码

export default function App() {  const translateX = useSharedValue<number>(0); ​  const handlePress = () => {    translateX.value += 50; }; ​  const animatedStyles = useAnimatedStyle(() => ({    transform: [{ translateX: withSpring(translateX.value * 2) }], })); ​  return (    <>      <Animated.View style={[styles.box, animatedStyles]} />      <View style={styles.container}>        <Button onPress={handlePress} title="Click me" />      </View>    </> );

useAnimatedStyle 钩子可以处理复杂逻辑,允许我们访问处理共享值。

向右移动动画.gif

props动画

大多数的动画是通过style来修改的,但还有通过props传递样式的,比如在 SVG 中我们是传递props,而不是通过style。


ini

代码解读

复制代码

<Circle cx="50" cy="50" r="10" fill="blue" />

上文提到,只有RN内置组件存在于Animated中,SVG是没有动画效果的,我们需要通过createAnimatedComponent 来让SVG变成动画组件。


javascript

代码解读

复制代码

import Animated from 'react-native-reanimated'; import { Circle } from 'react-native-svg'; ​ const AnimatedCircle = Animated.createAnimatedComponent(Circle);

创建共享值来控制SVG的半径:


javascript

代码解读

复制代码

import { useSharedValue } from 'react-native-reanimated'; import { Svg } from 'react-native-svg'; ​ function App() {  const r = useSharedValue(10); ​  return (    <Svg>      <AnimatedCircle cx="50" cy="50" r={r} fill="blue" />    </Svg> ); }

使用 useAnimatedProps 封装动画props,让SVG的半径每次增加10px


ini

代码解读

复制代码

const AnimatedCircle = Animated.createAnimatedComponent(Circle); ​ export default function App() {  const r = useSharedValue<number>(20); ​  const handlePress = () => {    r.value += 10; }; ​  const animatedProps = useAnimatedProps(() => ({    r: withTiming(r.value), })); ​  return (    <View style={styles.container}>      <Svg style={styles.svg}>        <AnimatedCircle          cx="50%"          cy="50%"          fill="#b58df1"          animatedProps={animatedProps}        />      </Svg>      <Button onPress={handlePress} title="Click me" />    </View> ); }

useAnimatedPropsuseAnimatedStyles 使用方法一样,使用场景不同,一个是创建props中的动画,一个是创建style中的动画。

圆形增大动画.gif

创建复杂动画

下面我们探讨如何使用 Reanimated 的内置动画函数(withRepeatwithSequencewithDelay)来创建复杂且引人入胜的动画效果。

Renimated 动画函数简介

Reanimated 提供了三种内置的动画修饰符,它们可以帮助开发者轻松地创建复杂的动画:

  1. withRepeat:允许重复一个动画指定的次数,或者无限次重复。
  2. withSequence:允许你按顺序执行多个动画。
  3. withDelay:在动画开始前添加延迟。

通过这些函数我们可以创建很多你想要的动画效果,下面我们通过实际例子来学习如何使用它们。

这是实际运行图:

3.gif

1. 基础样式

首先,我们需要设置一个基本的动画框架。我们将使用 useAnimatedStylewithTiming 来实现一个简单的左右移动动画。


ini

代码解读

复制代码

import { View, Button, StyleSheet } from 'react-native'; import { Animated, useSharedValue, useAnimatedStyle } from 'react-native-reanimated'; ​ export default function App() {  const offset = useSharedValue(0); ​  const style = useAnimatedStyle(() => ({    transform: [{ translateX: offset.value }], })); ​  const OFFSET = 40; ​  const handlePress = () => {    offset.value = withTiming(OFFSET); }; ​  return (    <View style={styles.container}>      <Animated.View style={[styles.box, style]} />      <Button title="Shake" onPress={handlePress} />    </View> ); } ​ const styles = StyleSheet.create({  container: {    flex: 1,    justifyContent: 'center',    alignItems: 'center', },  box: {    width: 100,    height: 100,    backgroundColor: 'blue', }, });

在这个示例中,点击按钮后,动画框会平滑地移动到右侧 40px 的位置。

1.gif

2. 添加重复动画:withRepeat

为了让动画框左右摇动,我们可以使用 withRepeat 修饰符。这个修饰符允许我们重复一个动画指定的次数,并且可以选择是否反向执行。


ini

代码解读

复制代码

import { withRepeat } from 'react-native-reanimated'; ​ const handlePress = () => {  offset.value = withRepeat(withTiming(OFFSET), 5, true); };

第二个参数为重复的次数,可以通过传递一个非正值(例如 0 或 -1 )使其永远重复。通过向第三个参数传递 true 来使动画来回运行。

2.gif

3. 添加顺序动画:withSequence

目前的动画不会完整的左右摆动,而且没有停到原来的位置。为了实现这一点,我们可以使用 withSequence 修饰符来按顺序执行多个动画。


less

代码解读

复制代码

import { withSequence } from 'react-native-reanimated'; ​ const TIME = 250; ​ const handlePress = () => {  offset.value = withSequence(    withTiming(-OFFSET, { duration: TIME / 2 }), // 先向左移动    withRepeat(withTiming(OFFSET, { duration: TIME }), 5, true), // 摇动    withTiming(0, { duration: TIME / 2 }) // 回到初始位置 ); };

通过 withSequence,我们可以将动画分解为三个部分:

  1. 先向左移动到 -OFFSET
  2. 重复执行左右摇动的动画。
  3. 最后回到初始位置 0

4.gif

4. 添加延迟:withDelay

为了让动画更加有趣,我们可以在动画开始前添加一个短暂的延迟。这可以通过 withDelay 修饰符实现。


ini

代码解读

复制代码

import { withDelay } from 'react-native-reanimated'; ​ const DELAY = 400; ​ const handlePress = () => {  offset.value = withDelay(    DELAY,    withSequence(      withTiming(-OFFSET, { duration: TIME / 2 }),      withRepeat(withTiming(OFFSET, { duration: TIME }), 5, true),      withTiming(0, { duration: TIME / 2 })   ) ); };

在这里,我们设置了一个 400 毫秒的延迟,动画会在延迟后开始执行。

完整代码

javascript

代码解读

复制代码

import { View, Button, StyleSheet } from 'react-native'; import { Animated, useSharedValue, useAnimatedStyle } from 'react-native-reanimated'; import { withRepeat, withSequence, withDelay } from 'react-native-reanimated'; ​ export default function App() {  const offset = useSharedValue(0); ​  const style = useAnimatedStyle(() => ({    transform: [{ translateX: offset.value }], })); ​  const OFFSET = 40;  const TIME = 250;  const DELAY = 400; ​  const handlePress = () => {    offset.value = withDelay(      DELAY,      withSequence(        withTiming(-OFFSET, { duration: TIME / 2 }), // 先向左移动        withRepeat(withTiming(OFFSET, { duration: TIME }), 5, true), // 摇动        withTiming(0, { duration: TIME / 2 }) // 回到初始位置     )   ); }; ​  return (    <View style={styles.container}>      <Animated.View style={[styles.box, style]} />      <Button title="Shake" onPress={handlePress} />    </View> ); } ​ const styles = StyleSheet.create({  container: {    flex: 1,    justifyContent: 'center',    alignItems: 'center', },  box: {    width: 100,    height: 100,    backgroundColor: 'blue', }, });

小结

本文详细介绍了 React Native Reanimated 的基础用法,包括如何创建共享值、使用 Animated 组件、通过 useAnimatedStyleuseAnimatedProps 实现复杂动画逻辑,以及如何利用内置动画函数(withRepeatwithSequencewithDelay)创建复杂的动画效果。通过这些工具,开发者可以轻松实现各种动画需求,提升应用的用户体验。

作者:冰镇白干
链接:https://juejin.cn/post/7470331424588038195
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐