React Native Animated

521 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

上文介绍了如何使用LayoutAnimation,本文主要介绍另外一套用于创建更精细动画的Animated

API介绍

1. 动画值

RN提供了多种动画值对象,用来存储在动画过程中值的变化,其中包括:

  • 基础动画值:Animated.ValueAnimated.ValueXY来表示一维或二维向量
  • 合成动画值:Animated.addAnimated.subtract等基于数字或其他基础动画值来进行二次计算的动画值

动画值有几个比较有意思的API,下面简单说明一下:

  • setValue: 设置新的值,次方法会停止动画
  • setOffset: 设置一个偏移量,在接下来的动画中,获取到的值都会加上这个偏移量,常用在拖拽操作中
  • flattenOffset:将偏移量合并到值里,然后将偏移量设置为0,常用在拖拽操作中
  • interpolate: 差值,可以将值映射成为新的值,例如动画从01的变化,我们可以通过差值映射成为0100的变化

2. 动画函数

RN提供了三个方法用于创建动画:

  1. Animated.timing: 线性效果,驱动动画值按照给定的过度曲线随时间变化
  2. Animated.decay: 衰变效果,驱动动画值以一个初速度和给定的衰减系数变化
  3. Animated.spring: 弹簧效果,根据阻尼谐振动的弹性模型驱动动画值变化

3. 动画组件

光有动画值和动画函数,只能驱动值变化,我们还需要对应的动画组件来绑定动画值,最终将动画值的变化体现在界面上,RN内置了以下支持动画的组件:

  • FlatList
  • Image
  • ScrollView
  • SectionList
  • Text
  • View

只有动画组件才支持将动画值设置在props上,动画组件内部通过一系列的转化,将动画值转换为props。我们也可以通过createAnimatedComponentAPI拓展其他组件支持动画。

基本使用

下面通过一个简单的例子体验一下Animated创建动画的过程

import { Animated, View } from 'react-native';
import React, { useRef } from 'react';
import { MyButton } from './MyButton';

export const ShowAnimatedAnimation = () => {
  const animateValue = useRef(new Animated.Value(100)).current;
  const start = () => {
    Animated.timing(animateValue, {
      toValue: 200,
      duration: 1000,
      useNativeDriver: false,
    }).start(() => console.log(`end`));
  };

  const reset = () => {
    Animated.timing(animateValue, {
      toValue: 100,
      duration: 1000,
      useNativeDriver: false,
    }).start();
  };
  return (
    <View
      style={{
        flex: 1,
        width: '100%',
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <Animated.View
        style={{
          width: animateValue,
          height: animateValue,
          backgroundColor: '#abc',
        }}
      />
      <View
        style={{
          position: 'absolute',
          bottom: 30,
          flexDirection: 'row',
        }}>
        <MyButton title={'start'} onPress={start} />
        <MyButton title={'reset'} onPress={reset} />
      </View>
    </View>
  );
};

效果如下:

React_Native_Animated_1.gif

在上面代码中,我们创建了一个名为animateValueAnimated.Value,将Animated.View的宽度高度和animateValue绑定。

当组件第一次加载时,animateValue初始值为100,在点击按钮之后,timing动画开始执行,animateValue中存储的会随着动画而改变,对应绑定的宽度和高度开始变化,在经过配置中设置的1000ms之后,withheight变为200。

配置动画

从上面的示例中可以看到,动画函数有不同的配置项,不同的配置项会产生不同的动画效果,下面我们以相同的平移动画来看看在不同动画函数和配置下产生的动画效果。

1. Animated.timing

配置参数说明如下:

toValue: 动画目标值
easing?: 线性函数
duration?: 动画持续时长
delay?: 延迟开始时间

效果

React_Native_Animated_2.gif

Easing类中内置了几类常用的线性函数,示例中选取了linearcubic

2. Animated.decay

配置参数说明如下:

velocity: 初始速度
deceleration?: 衰变系数,默认值为0.997,衰减系数越小,衰减幅度越大

效果

React_Native_Animated_3.gif

decay动画无法显示控制动画时长,它由初始速度和衰减系数共同确定,通常用在手势交互中,模拟正常的滑动衰减动画,velocity传入手指离开时的速度,deceleration用默认值。

3. Animated.spring

配置参数说明如下:

// 1
bounciness: 控制弹性,默认值8
speed: 控制动画速度,默认值12

// 2
tension: 控制速度,默认值40
friction: 控制弹性/幅度,默认值7

// 3
stiffness: 弹簧刚性系数,默认值100
damping: 如何因摩擦力而阻尼弹簧的运动,默认值10
mass: 附在弹簧末端的物体的质量,默认值1

与其他两种动画的参数设置不同,spring动画提供了三组设置,并且同时只能选用其中一套设置,上面两组符合facebook pop或者origami中的sping模型定义,第三组则是使用基于阻尼谐振子运动方程的分析弹簧模型,简单来说更贴近于现实,这也是RN spring动画中默认的实现方式,当我们什么参数都不修改的时候,RN会使用第三组默认值来进行动画。

组一效果

React_Native_Animated_4.gif

组二效果

React_Native_Animated_5.gif

组三效果

React_Native_Animated_6.gif

spring除了可以配置上面主要的三组数据之外,还有一些其他属性可以配置,这里就不一一展示了,有兴趣可以clone工程到本地测试一下。

组合动画

上面的动画都是描述的单一动画类型,有的时候我们需要按一定顺序执行一组动画,RN也提供了以下API方便我们进行动画的组合:

1. Animated.parallel

该API可以同时一组动画,默认情况下,有任何一个动画停止了,其余动画会停止,可以通过设置来取消这种关联

Animated.parallel([ Animated.timing(...), Animated.spring(...)], { stopTogether: false }).start()

2. Animated.sequence

该API按顺序执行一组动画,等待一个完成后再执行下一个,如果当前动画被终止,则后面的动画不会继续执行

Animated.parallel([ Animated.timing(...), Animated.spring(...)]).start()

3. Animated.stagger

该API会通过设置的延时时间来开始下一个动画,并不关心前一个动画的执行状态,所以有可能出现同时执行多个动画的情况

Animated.parallel(2000, [ Animated.timing(...), Animated.spring(...)]).start()

上面的伪代码中,会立即开始timing动画,然后在设置的2000ms之后开始spring动画。

插值

所有的动画值都可以进行插值操作,插值允许我们将一定范围输入值映射到另一组不同的输出值上,例如从范围01的值转化为0100的值

value.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100]
});

如果是timing动画的话,对于类似的线性的映射,我们可以通过使用指定的easing函数。

多区间映射

除了上面比较简单的一个区间到另一个区间的简单映射之外,插值还支持多个区间段落,下面示例表示输入值从0到1时,输出值从0到-100,当输入值从1到2时,输出值从-100到100:

value.interpolate({
  inputRange: [0, 1, 2],
  outputRange: [0, -100, 100]
});

在假设是线性变化的情况下,最终映射结果如下:

| 输入  | 输出 |
| ---- | ---- |
| 0    | 0    |
| 0.5  | -50  |
| 1    | -100 |
| 1.5  | 0    |
| 2    | 100  |

字符映射

除了常见的数字到数字的映射之外,插值还支持从数字到字符串的映射,最常见的就是从数字到角度的映射,例如下面示一个先快后慢,从0到360度的旋转动画:

value.interpolate({
  inputRange: [0, 0.5, 1],
  outputRange: ['0deg', '300deg', '360deg']
});

在线性变化的情况下,最终映射结果如下:

| 输入  | 输出 |
| ---- | ---- |
| 0    | '0deg'  |
| 0.25 | '150deg'|
| 0.5  | '300deg'|
| 0.75 | '330deg'|
| 1    | '360deg'|

另一种比较常见的颜色映射也可以理解为字符串映射的一种,将颜色转为对应的rgb数值,然后再进行映射,这里就不一一列举了。

关于Animated的介绍到这里就告一段落了,还有很多其他关于Animated的特性,例如与手势的结合,如何自定义动画组件等,这里就不展开了。本篇中涉及效果的实现可以在这里这里找到。

参考链接:

  1. React Native 动画
  2. React Native API