简单使用react-native的Animated动画

在项目中简单使用了 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>
    )
  }
}

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,961评论 3 119
  • 尘事空劳如一梦,几多琐屑误年华。 人生何处寻诗意?微雨空庭数落花。
    过雨采蘋阅读 542评论 1 5
  • 文/宝木笑 上高中的时候,学习压力非常大,从高一开始就像进入高考倒计时了,各种考试和排名次整的同窗之间仿佛江湖仇敌...
    宝木笑阅读 870评论 6 12
  • 他说:萤火是夏夜的光 她抬头指着月亮,调皮地说:那它呢 他和她牵手走在安静的操场 她说:这里没有很多 夜亦或是你 ...
    原小尚阅读 255评论 4 1
  • 总是在想你的时候喝一杯红酒 假装是个高雅的嗜好 假装在那喧哗的世界里过得很好 假装自己并不孤独 假装一切与你无关 ...
    Spancer_Wu阅读 372评论 1 0