欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

简单使用react-native的Animated动画

程序员文章站 2024-01-14 16:39:34
...

在项目中简单使用了 react-native 的 Animated 动画,这里介绍项目中使用到的两种场景。
场景一:点击关闭一个弹窗时,弹窗会慢慢变小到消失,同时运动轨迹是慢慢从中心到标题栏的右侧。该场景是在用户关闭 APP 的功能介绍弹窗时,能够让用户下次需要了解该功能时知道从哪里获取信息。
场景二:点击切换标签页时,被**的标签底部的横线滑动的动画效果。这个功能很多插件都自带这个功能,不需要额外定义,我也是在使用 react-native-scrollable-tab-view 库时添加了一些额外的标签栏样式时,需要自行添加改动画。

Animated 动画

Animated 动画组件

Animated 中默认导出了以下这些可以直接使用的动画组件:

  • Animated.Image
  • Animated.ScrollView
  • Animated.Text
  • Animated.View

我们还可以使用 createAnimatedComponent() 方法自定义动画组件,下面示例中演示了创建一个可点击的动画组件:

import { TouchableWithoutFeedback, Animated } from 'react-native';
const AnimatedTouchableWithoutFeedback = Animated.createAnimatedComponent(TouchableWithoutFeedback);

// ...
<AnimatedTouchableWithoutFeedback onPress={() => {...}}>
  <Animated.View style={{ opacity: this.state.fadeInOpacity }} />
</AnimatedTouchableWithoutFeedback>

两种类型的值

Animated 提供了两种类型的值:

  • Animated.Value():用于单个值
  • Animated.ValueXY():用于矢量值

配置动画

Animated 提供了三种动画类型。每种动画类型都提供了特定的函数曲线,用于控制动画值从初始值变化到最终值的变化过程:

  • Animated.timing():线性变化,使用 easing 函数让数值随时间动起来。
  • Animated.decay():衰变效果,以一个初始的速度和一个衰减系数逐渐减慢变为0。。
  • Animated.spring():弹簧效果,提供了一个简单的弹簧物理模型.

大多数情况下使用 timing()就可以了。默认情况下,它使用对称的 easeInOut 曲线,将对象逐渐加速到全速,然后通过逐渐减速停止结束。

组合动画

动画还可以使用组合函数以复杂的方式进行组合:

  • Animated.delay():动画延迟,在给定延迟后开始动画。
  • Animated.parallel():同时启动多个动画。默认情况下,如果有任何一个动画停止了,其余的也会被停止。可以通过stopTogether 选项设置为 false 来取消这种关联。
  • Animated.sequence():按顺序启动动画,等待每一个动画完成后再开始下一个动画。如果当前的动画被中止,后面的动画则不会继续执行。
  • Animated.stagger():按照给定的延时间隔,顺序并行的启动动画。即在前一个动画开始之后,隔一段指定时间开始执行下一个动画,并不关心前一个动画是否已经完成,所以有可能会出现多个动画同时执行的情况。

使用示例

创建动画最简单的工作流程是创建一个 Animated.Value ,将它连接到动画组件的一个或多个样式属性,然后使用 Animated.timing() 等动画效果展示数据的变化,tart/stop 方法来控制基于时间的动画执行。示例中透明度 new Animated.Value(0) ,首先设为 0,后面 timing 动画中 toValue: 1,即最终变为完全不透明。我们也可以定义一个渐隐的效果,从 1 变为 0,组件就会慢慢消失。

import React from 'react';
import { Animated, Text, View } from 'react-native';

class FadeInView extends React.Component {
  state = {
    fadeInOpacity: new Animated.Value(0),  // 透明度初始值设为0
  }
  componentDidMount() {
    Animated.timing(                       // 随时间变化而执行动画
      this.state.fadeInOpacity,            // 动画中的变量值
      {
        toValue: 1,                        // 透明度最终变为1,即完全不透明
        duration: 10000,                   // 让动画持续一段时间
      }
    ).start();                             // 开始执行动画
  }
  render() {
    const { fadeInOpacity } = this.state;

    return (
      <Animated.View                       // 使用专门的可动画化的View组件
        style={{
          ...this.props.style,
          opacity: fadeInOpacity,          // 将透明度指定为动画变量值
        }}
      >
        {this.props.children}
      </Animated.View>
    );
  }
}

场景一示例

class Comp1 extends React.Component<IProps, {}> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      isModalVisible: false,                      // 弹窗是否可见
      modalWidth: new Animated.Value(1),          // 弹窗初始宽度
      modalHeight: new Animated.Value(1),         // 弹窗初始高度
    };
  }

  startAnimated = () => {
    // 同步执行的动画
    Animated.parallel([
      Animated.timing(this.state.modalHeight, {
        toValue: 0,
        duration: 500,
        easing: Easing.linear,
      }),
      Animated.timing(this.state.modalWidth, {
        toValue: 0,
        duration: 500,
        easing: Easing.linear,
      }),
      // 可以添加其他动画
    ]).start(() => {
      // 这里可以添加动画之后要执行的函数
      setTimeout(() => {
        this.setState({ isModalVisible: false });
      }, 100);
    });
  };
  
  render() {
    const modalWidth = this.state.modalWidth.interpolate({
      inputRange: [0, 1],
      outputRange: [1, 300],
    });
    const modalHeight = this.state.modalHeight.interpolate({
      inputRange: [0, 1],
      outputRange: [1, 200],
    });

    return (
      <View>
        {/* 其他组件... */}

        <Animated.View
          style={[
            styles.modalContent,
            {
              width: modalWidth,
              height: modalHeight,
            },
          ]}
        >
          <View style={{ // ... }}>
            <Animated.Text style={[styles.title, { fontSize: titleFontSize }]}>标题</Animated.Text>
            <ScrollView>
              <Animated.Text style={[styles.content, { fontSize: contentFontSize }]}>这是提示文本,这是提示文本,这是提示文本。</Animated.Text>
            </ScrollView>
            <Button onClick={() => {}}>
              <Animated.Text style={{ fontSize: titleFontSize }}>跳转</Animated.Text>
            </Button>
          </View>
          <TouchableOpacity
            style={{ marginTop: 20, padding: 10, alignItems: 'center' }}
            onPress={this.startAnimated}
          >
            {/* 关闭按钮 */}
          </TouchableOpacity>
        </Animated.View>
      </View>
    )
  }
}

场景二示例

class Comp2 extends React.Component<IProps, {}> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      lineWidth: new Animated.Value(0),
      lineLeft: new Animated.Value(0),
      prevWidth: 0,
      prevLeft: 0,
      nextWidth: 0,
      nextLeft: 0,
    };
  }

  componentDidUpdate() {
    if (// ...) {
      this.startAnimated();
      this.setState((prevState: any, props: IProps) => ({
        prevLeft: prevState.nextLeft,
        prevWidth: prevState.nextWidth,
        nextLeft: this.tabbarInfos[activeTab].left,
        nextWidth: this.tabbarInfos[activeTab].width,
      }));
    }
  }

  startAnimated = () => {
    this.state.lineWidth.setValue(0);
    this.state.lineLeft.setValue(0);
    Animated.parallel([
      Animated.timing(this.state.lineWidth, {
        toValue: 1,
        duration: 200,
        easing: Easing.linear,
      }),
      Animated.timing(this.state.lineLeft, {
        toValue: 1,
        duration: 200,
        easing: Easing.linear,
      }),
    ]).start();
  };
  
  render() {
    const lineLeft = this.state.lineLeft.interpolate({
      inputRange: [0, 1],
      outputRange: [this.state.prevLeft, this.state.nextLeft],
    });
    const lineWidth = this.state.lineWidth.interpolate({
      inputRange: [0, 1],
      outputRange: [this.state.prevWidth, this.state.nextWidth],
    });

    const tabUnderlineStyle = {
      position: 'absolute',
      height: 1,
      backgroundColor: '#666',
      bottom: 0,
      width: lineWidth,
      left: lineLeft,
    };

    return (
      <View>
        {/* 其他组件... */}

        <Animated.View style={[tabUnderlineStyle, { //... }]} />
      </View>
    )
  }
}

参考文章