1.前言

上一节我们学习了全局的布局动画api——LayoutAnimation,体验到其流畅柔和的动画效果,但有时我们需要实现一些更精细化的动画,或者完成一些组合动画,这时我们可以使用React Native提供的另一个高级动画api——Animated。
Animated使得我们可以非常容易地实现各种各样的动画和交互方式,并且具备极高的性能。Animated旨在以声明的形式来定义动画的输入与输出,通常只需要关注设置动画的开始和结束即可,在其中建立一个可配置的变化函数,然后使用start/stop方法来控制动画按顺序执行。
下面通过一个简单的淡入动画逐步展开介绍Animated的具体使用,还是基于上一节的LayoutAnimation的使用实例修改。

import React, { Component } from 'react';
import {StyleSheet,Text,View,TouchableOpacity,Platform,Image,Animated,Easing,
} from 'react-native';export default class AnimatedAnimationDemo extends Component {constructor(props) {super(props);// 初始状态this.state = {fadeInOpacity: new Animated.Value(0),};this._onPress = this._onPress.bind(this);}_onPress() {Animated.timing(this.state.fadeInOpacity, {toValue: 1,duration: 2000,easing: Easing.linear,// 线性的渐变函数}).start();}render() {return (<View style={styles.container}><Animated.View // 可选的基本组件类型: Image,Text,ScrollView,View(可以包裹任意子View) style={[styles.content, {opacity: this.state.fadeInOpacity,}]}><Text style={[{textAlign: 'center'}]}>Hello World!</Text></Animated.View><TouchableOpacity style={styles.content} onPress={this._onPress}><View style={styles.button}><Text style={styles.buttonText}>Press me!</Text></View></TouchableOpacity></View>);}
}const styles = StyleSheet.create({container: {marginTop:25,flex: 1,},content: {backgroundColor: 'rgba(200, 230, 255, 0.8)',marginBottom:10,justifyContent:"center",alignSelf:"center",},button: Platform.select({ios: {},android: {elevation: 4,// Material design blue from https://material.google.com/style/color.html#color-color-palettebackgroundColor: '#2196F3',borderRadius: 2,width:100,height:30,},justifyContent:"center",alignSelf:"center",}),buttonText: {alignSelf:"center",}
});

效果如下:

2.基本Animated动画的步骤

上例是一个基本的Animated动画的实现,其步骤可以分为以下几步:
1.使用Animated.Value设定一个或多个初始值,包括透明度、位置等。
2.使用基本的Animated封装的组件,如Animated.ViewAnimated.TextAnimated.ImageAnimated.ScrollView
3.将初始值绑定到动画目标属性上。
4.通过Animated.timeing等方法设定动画参数。
5.调用start控制动画的启动。

2.1值类型

Animated提供两种值类型

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

Animated.Value可以绑定到样式或是其他属性上,也可以进行插值运算。单个Animated.Value可以用在任意多个属性上。多数情况下,Animated.Value可以满足需求(上面的示例),但有些情况下我们可能会需要AnimatedValueXY。例如:需要某一组件沿着X轴和Y轴交叉方向,向右下移动一段距离。
上例中通过

  constructor(props) {super(props);this.state = {fadeOutOpacity: new Animated.Value(0),};...}

设置了组件透明度的初始值。

2.2支持的组件类型

支持Animated的组件类型:Animated.ViewAnimated.TextAnimated.ImageAnimated.ScrollView
你也可以使用Animated.createAnimatedComponent()来封装你自己的组件。不过该方法较少使用, 通常可以通过View包裹其他任意组件达到同样的效果。
上例中是对Animated.View组件进行动画设置。

2.3将初始值绑定到动画目标属性上

将动画绑定在<Animate.View /> 上,把实例化的动画初始值传入 style 中:

  <Animated.View style={[styles.content, {opacity: this.state.fadeInOpacity,}]}>...</Animated.View>

2.4配置动画并启动

通过Animated.timeing等方法设定动画参数,调用start控制动画的启动。

     Animated.timing(this.state.fadeInOpacity, {toValue: 1,duration: 2000,easing: Easing.linear,// 线性的渐变函数}).start();

以上便是一个简单Animated动画的实现。

2.5动画类型

上例中使用了Animated.timing方法基于时间配置实现渐变动画,Animated共提供以下三种动画类型:

  • spring:基础的弹跳物理模型动画
  • timing:带有时间的渐变动画
  • decay:以一个初始速度开始并且逐渐减慢停止的动画

这三个动画配置api是Animated的核心api, 具体定义如下:

  • static decay(value, config)
  • static timing(value, config)
  • static spring(value, config)

创建动画的参数:
@value:AnimatedValue | AnimatedValueXY(X轴或Y轴 | X轴和Y轴)
@config:SpringAnimationConfig | TimingAnimationConfig | DecayAnimationConfig(动画的参数配置)

分别列出各config的特性参数:

  • TimingAnimationConfig动画配置选项定义如下:
type TimingAnimationConfig = AnimationConfig & {toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,easing?: (value: number) => number,  //缓动函数,默认 Easing.inOut(Easing.ease).duration?: number,  //动画时长,单位毫秒,默认500delay?: number,  //动画执行延迟时间,单位:毫秒,默认为0
};
  • DecayAnimationConfig动画配置选项定义如下:
type DecayAnimationConfig = AnimationConfig & {velocity: number | {x: number, y: number},  //初始速度,必须要填写deceleration?: number,  //速度减小的比例,加速度。默认为0.997
};
  • SpringAnimationConfig动画配置选项定义如下:
type SpringAnimationConfig = AnimationConfig & {toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,overshootClamping?: bool,restDisplacementThreshold?: number,restSpeedThreshold?: number,velocity?: number | {x: number, y: number},   //初始速度,默认0bounciness?: number,      //反弹系数,默认8speed?: number,           //速度,默认12tension?: number,         //张力系数,默认7friction?: number,        //摩擦系数,默认40
};

注:只能定义其中一组:bounciness/speedtension/friction

下面通过逐步扩展丰富上面的例子,介绍Animated的更多特性。
先回到前面值类型AnimatedValueXY,我们如何实现向右下移动一段距离的动画。请看下例:

import React, { Component } from 'react';
import {StyleSheet,Text,View,TouchableOpacity,Platform,Image,Animated,Easing,
} from 'react-native';export default class AnimatedAnimationDemo2 extends Component {constructor(props) {super(props);this.state = {translateValue: new Animated.ValueXY({x:0, y:0}), // 二维坐标};this._onPress = this._onPress.bind(this);}_onPress() {this.state.translateValue.setValue({x:0, y:0});Animated.decay( // 以一个初始速度开始并且逐渐减慢停止。  S=vt-(at^2)/2   v=v - atthis.state.translateValue,{velocity: 7, // 起始速度,必填参数。deceleration: 0.1, // 速度衰减比例,默认为0.997。}).start();}render() {return (<View style={styles.container}><Animated.View style={[styles.content, {transform: [{translateX: this.state.translateValue.x}, // x轴移动{translateY: this.state.translateValue.y}, // y轴移动]}]}><Text style={[{textAlign: 'center'}]}>Hello World!</Text></Animated.View><TouchableOpacity style={styles.content} onPress={this._onPress}><View style={styles.button}><Text style={styles.buttonText}>Press me!</Text></View></TouchableOpacity></View>);}
}const styles = StyleSheet.create({container: {marginTop:25,flex: 1,},content: {backgroundColor: 'rgba(200, 230, 255, 0.8)',marginBottom:10,justifyContent:"center",alignSelf:"center",},button: Platform.select({ios: {},android: {elevation: 4,// Material design blue from https://material.google.com/style/color.html#color-color-palettebackgroundColor: '#2196F3',borderRadius: 2,width:100,height:30,},justifyContent:"center",alignSelf:"center",}),buttonText: {alignSelf:"center",}
});

通过new Animated.ValueXY({x:0, y:0})设置了在xy轴上的初始坐标。
其中,组件Animated.View的属性transform是一个变换数组,常用的有scale, scaleX, scaleY, translateX, translateY, rotate, rotateX, rotateY, rotateZ,其使用方式可以如下:

transform: [  // scale, scaleX, scaleY, translateX, translateY, rotate, rotateX, rotateY, rotateZ{scale: this.state.bounceValue},  // 缩放{rotate: this.state.rotateValue.interpolate({ // 旋转,使用插值函数做值映射inputRange: [0, 1],outputRange: ['0deg', '360deg']})},{translateX: this.state.translateValue.x}, // x轴移动{translateY: this.state.translateValue.y}, // y轴移动
],
]

3插值函数interpolate

在transform的使用示例中使用了interpolate插值函数。这个函数实现了数值大小、单位的映射转换,允许一个输入的区间范围映射到另外一个输入的区间范围。比如:将0-1数值转换为0deg-360deg角度,旋转View时:

this.state.rotateValue.interpolate({ // 旋转,使用插值函数做值映射inputRange: [0, 1],outputRange: ['0deg', '360deg']})

具体的实例:
还是在上例的基础上,初始值改为:

this.state = {translateValue: new Animated.Value(1),};

<Animated.View />的style样式中的transform属性改为:

transform: [{scale: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: [1, 3],})},{translateX: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: [0, 300],})},{rotate: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: ['0deg', '720deg'],})},]

动画配置改为:

    Animated.spring(this.state.translateValue, {toValue: 0,velocity: 7,tension: -20,friction: 3,}).start();

全部代码如下(篇幅原因,省略import与样式代码,同上例):

...
export default class AnimatedAnimationDemo2 extends Component {constructor(props) {super(props);this.state = {translateValue: new Animated.Value(1),};this._onPress = this._onPress.bind(this);}_onPress() {Animated.spring(this.state.translateValue, {toValue: 0,velocity: 7,tension: -20,friction: 3,}).start();}render() {return (<View style={styles.container}><Animated.View style={[styles.content, {transform: [{scale: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: [1, 3],})},{translateX: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: [0, 300],})},{rotate: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: ['0deg', '720deg'],})},]}]}><Text style={[{textAlign: 'center'}]}>Hello World!</Text></Animated.View><TouchableOpacity style={styles.content} onPress={this._onPress}><View style={styles.button}><Text style={styles.buttonText}>Press me!</Text></View></TouchableOpacity></View>);}
}
...

效果如下:

4组合动画

Animated可以通过以下方法将多个动画组合起来执行:
* parallel:同时执行
* sequence:顺序执行
* stagger:错峰,其实就是插入了delay的parrllel
* delay:组合动画之间的延迟方法,严格来讲,不算是组合动画

4.1串行动画

下面用sequence演示一个顺序执行的串行动画。
还是在上例的基础上只用修改动画配置,在Animated.sequence中顺序执行Animated.springAnimated.delayAnimated.timing方法。

 _onPress() {Animated.sequence([Animated.spring(this.state.bounceValue,{toValue:1}),  Animated.delay(500),Animated.timing(this.state.rotateValue, {toValue: 1,duration: 800,easing: Easing.out(Easing.quad),})]).start(() => this._onPress());}

设定初始值:

constructor(props) {super(props);this.state = {bounceValue: new Animated.Value(0),rotateValue: new Animated.Value(0),};this._onPress = this._onPress.bind(this);}

<Animated.View />的style中的transform属性改为:

    transform: [{rotate: this.state.rotateValue.interpolate({inputRange: [0, 1],outputRange: ['0deg', '360deg'],})},{scale:this.state.bounceValue,}]

完整代码如下AnimatedAnimationDemo2 .js(省略了未修改代码):

...
export default class AnimatedAnimationDemo2 extends Component {constructor(props) {super(props);this.state = {bounceValue: new Animated.Value(0),rotateValue: new Animated.Value(0),};this._onPress = this._onPress.bind(this);}_onPress() {Animated.sequence([Animated.spring(this.state.bounceValue,{toValue:1}),Animated.delay(500),Animated.timing(this.state.rotateValue, {toValue: 1,duration: 800,easing: Easing.out(Easing.quad),})]).start(() => this._onPress());}render() {return (<View style={styles.container}><Animated.View style={[styles.content, {transform: [{rotate: this.state.rotateValue.interpolate({inputRange: [0, 1],outputRange: ['0deg', '360deg'],})},{scale:this.state.bounceValue,}]}]}><Text style={[{textAlign: 'center'}]}>Hello World!</Text></Animated.View><TouchableOpacity style={styles.content} onPress={this._onPress}><View style={styles.button}><Text style={styles.buttonText}>Press me!</Text></View></TouchableOpacity></View>);}
}
...

效果如下:先拉伸,延迟500毫秒后再旋转

4.2并行动画

说完串行动画,很自然的我们会想到并行动画。并行动画主要通过Animated.parallel方法将多个动画并行同时执行。可修改上述的_onPress方法:

_onPress() {Animated.parallel([Animated.spring(this.state.bounceValue, {toValue: 1,}),Animated.timing(this.state.rotateValue, {toValue: 1,easing: Easing.elastic(1),})]).start();}

效果如下:拉伸同时旋转

5动画循环

Animated的start方法可以接受一个回调函数,在动画或某个流程结束的时候执行,通过该方法监听动画的结束在回调函数中再次执行上例中的_onPress即可重复执行动画:

_onPress() {this.state.translateValue.setValue(0);Animated.timing(this.state.translateValue, {toValue: 1,duration: 800,easing: Easing.linear}).start(() => this._onPress());}

<Animated.View />中style的transform属性改为:

transform: [{rotate: this.state.translateValue.interpolate({inputRange: [0, 1],outputRange: ['0deg', '360deg'],})},]

实现如下循环动画效果:

6追踪动态值

React Native动画支持跟踪功能,只需要将toValue设置成另一个动态值而不是一个普通数字即可,比如可以通过Animated.timing设置duration:0来实现快速跟随的效果。它们还可以使用插值来进行组合:

Animated.spring(follower, {toValue: leader}).start();
Animated.timing(opacity, {toValue: pan.x.interpolate({inputRange: [0, 300],outputRange: [1, 0],}),
}).start();

7手势控制动画

除了上述自发进行的动画外,有时候我们需要根据Scroll或者手势来手动的控制动画的过程。Animated.event是实现手势控制动画的关键,允许手势或其它事件直接绑定到动态值上。这里的Aniamted.event的输入是一个数组,用来做数据绑定 。
在ScrollView中:
我们把event.nativeEvent.contentOffset.x的值赋值到scrollX变量中(event一般为回调方法的第一个参数):

onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}]   //把contentOffset.x绑定给this.state.xOffset
)}

在Pan手势中:
把手势状态的gestureState.dxgestureState.dy的值赋值到pan.x何pan.y变量中(gestureState通常为PanResponder回调方法中的第二个参数):

onPanResponderMove={Animated.event([null,                                          // 忽略原生事件{dx: pan.x, dy: pan.y}                         // 从gestureState中解析出dx和dy的值
]);

7.1Scroll驱动

目标效果如下:随着ScrollView的向左滑动,最左边的一个Image透明度逐渐降低为0。

实例代码如下:

//AnimatedScrollDemo.js
import React, { Component } from 'react';
import {StyleSheet,Text,View,TouchableOpacity,Platform,Image,Animated,Easing,ScrollView
} from 'react-native';let deviceHeight = require('Dimensions').get('window').height;
let deviceWidth = require('Dimensions').get('window').width;
export default class AnimatedScrollDemo extends React.Component {state: {xOffset: Animated,};constructor(props) {super(props);this.state = {xOffset: new Animated.Value(1.0)};}render() {return (<View style={styles.container}><ScrollView horizontal={true} //水平滑动showsHorizontalScrollIndicator={false}style={{width:deviceWidth,height:deviceHeight}}//设置大小onScroll={Animated.event([{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x绑定给this.state.xOffset)}scrollEventThrottle={100}//onScroll回调间隔><Animated.Image source={require('../images/1.jpg')}style={{height:deviceHeight,width:deviceWidth,opacity:this.state.xOffset.interpolate({//映射到0.0,1.0之间inputRange: [0,375],outputRange: [1.0, 0.0]}),}}resizeMode="cover"/><Image source={require('../images/2.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" /></ScrollView></View>);}
}const styles = StyleSheet.create({container: {marginTop:25,flex: 1,},
});

7.2手势驱动

React Native最常用的手势就是PanResponser,由于本文侧重讲解动画,所以不会详细介绍PanResponser,仅仅介绍用到的几个属性和回调方法。关于手势的详细介绍会在后面的文章中介绍。

onStartShouldSetPanResponder: (event, gestureState) => {}//是否相应pan手势
onPanResponderMove: (event, gestureState) => {}//在pan移动的时候进行的回调
onPanResponderRelease: (event, gestureState) => {}//手离开屏幕
onPanResponderTerminate: (event, gestureState) => {}//手势中断

这些方法中都需要输入event与gestureState参数。
* 通过event可以获得触摸的位置,时间戳等信息。
* 通过gestureState可以获取移动的距离,速度等。

目标效果如下:View随着手拖动而移动,手指离开会到原点。

实例代码:

//AnimatedGestureDemo.js
import React, { Component } from 'react';
import {StyleSheet,Text,View,TouchableOpacity,Platform,Image,Animated,Easing,PanResponder
} from 'react-native';export default class AnimatedGestureDemo extends Component {state:{trans:AnimatedValueXY,}_panResponder:PanResponder;constructor(props) {super(props);this.state = {trans: new Animated.ValueXY(),};this._panResponder = PanResponder.create({onStartShouldSetPanResponder: () => true, //响应手势onPanResponderMove: Animated.event([null, {dx: this.state.trans.x, dy:this.state.trans.y}] // 绑定动画值),onPanResponderRelease: ()=>{//手松开,回到原始位置Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}).start();},onPanResponderTerminate:()=>{//手势中断,回到原始位置Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}).start();},});}render() {return (<View style={styles.container}><Animated.View style={{width:80,height:80,borderRadius:40,backgroundColor:'blue',transform:[{translateY:this.state.trans.y},{translateX:this.state.trans.x},],}}{...this._panResponder.panHandlers}></Animated.View></View>);}
}const styles = StyleSheet.create({container: {marginTop:25,flex: 1,},
});

监听当前的动画值

  • addListener(callback):动画执行过程中的值
  • stopAnimation(callback):动画执行结束时的值
    监听AnimatedValueXY类型translateValue的值变化:
this.state.translateValue.addListener((value) => {   console.log("translateValue=>x:" + value.x + " y:" + value.y);
});
this.state.translateValue.stopAnimation((value) => {   console.log("translateValue=>x:" + value.x + " y:" + value.y);
});

监听AnimatedValue类型translateValue的值变化:

this.state.translateValue.addListener((state) => {   console.log("rotateValue=>" + state.value);
});
this.state.translateValue.stopAnimation((state) => {   console.log("rotateValue=>" + state.value);
});

8小结

今天介绍了Animated的常用方法,并列举了相关的代码示例,例子比较多,对于有些效果有所省略,建议读者多动手尝试下每个效果,举一反三,加深理解。

react native学习笔记29——动画篇 Animated高级动画相关推荐

  1. React Native学习笔记一之搭建开发环境

    因为项目需要,今天开始正式学习React Native,先来搭建个开发环境 忐忑的心情 因为项目比较急,而且客户要求使用React Native开发,只能先学点基础然后在项目中使用的时候,边做边学了, ...

  2. react native学习笔记13——FlatList上拉加载

    我们可以利用官方组件RefreshControl实现下拉刷新功能,但React Native官方没有提供相应的上拉加载的组件,因此在RN中实现上拉加载比下拉刷新要复杂一点. 虽然没有直接提供上拉加载的 ...

  3. react native 学习笔记

    编译第一坑 Error type 3. Activity class {com.awesome_project/ com.awesome_project.MainActivity} does not ...

  4. React Native学习笔记-1:JSC profiler is not supported.(转载)

    运行react-native中Example下的UIEXPLORER Project 遇到虾面报错: 2016-03-21 14:12:18.941 [trace][tid:com.facebook. ...

  5. React Native 学习笔记六(关于宽高的设置)

    继续在之前的例子上进行添加 尺寸 1.使用固定的尺寸  设置View容器  和设置自定义的组组件  如果父组件的空间不足  自控件的会出现重叠的情况 示例: import {AppRegistry,S ...

  6. React Native 学习资源精选仓库

    <React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开源App.资源下载.以及相关新闻等,只求精不求全.因后面无法 Pu ...

  7. React Native 学习资源精选仓库(汇聚知识,分享精华)

    React Native 学习资源精选仓库(汇聚知识,分享精华) <React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开 ...

  8. JavaScript学习笔记之入门篇

    JavaScript学习笔记之入门篇 JavaScript引入 1. 页面级 js: 2. 外部js文件: JavaScript变量 1. 变量的作用: 2. 声明变量: 3. 变量赋值: 4. 单一 ...

  9. React Native学习提纲

    React Native学习提纲 当前版本最后修订日期: 2015年10月21日 一. React.js入门基础 1.基础HTML/CSS与基础开发工具使用 - html基础 doctype.常用标签 ...

最新文章

  1. Oracle SQL高级编程——分析函数(窗口函数)全面讲解
  2. java中setid(),Java Process.setId方法代碼示例
  3. linux运维有前途么,想去做linux运维,不知道有前途么?
  4. 关于 Nuxt 集成ueditor的一些坑(包括图片上传)前端部分
  5. [vue-element] 你有二次封装过ElementUI组件吗?
  6. 【软件开发底层知识修炼】七 Binutils辅助工具之- ar工具与nm工具
  7. 编译 linux 3,linux内核的编译(3)
  8. 2018年最值得关注的15大技术趋势
  9. C语言中字符串的处理方式(一)
  10. pytorch1.0神经网络保存、提取、加载
  11. zabbix--从入门到精通之zabbix历史数据
  12. linux ubuntu内核安装位置,在Ubuntu中安装或升级内核 linux kernel
  13. 计算机信应用技术,计算机信息应用技术.ppt
  14. console application
  15. 如何让计算机桌面字体变大,如何把字体放大 如何更改桌面与网页字体大小
  16. 等级保护三级信息系统安全设计
  17. Dell电脑重装系统
  18. 服务(Service)
  19. App集成ApplePay
  20. Selenium启动项参数设置

热门文章

  1. 群晖 Drive 的团队文件夹显示 “无法取得“
  2. android 8华为屏幕录制,数码知识:华为nova8pro如何屏幕录制怎么录屏
  3. Adobe Premiere Pro CC 2018 剪裁音频文件攻略
  4. php增加vip等级设置,会员管理系统中商家如何设置会员等级
  5. 三菱PLC模板程序FX5U轴FB块 使用ST语言编写的轴FB块,包含原点复归,点动,定位运动
  6. Shader Graph学习(一)
  7. *** Cisco路由器
  8. 计算机二级柏林是第几套,2019年计算机二级Office考试内容及时间安排(内附两套原题库)...
  9. 穆穆推荐-软件销售行业软件公司销售参考操作手册-之5-软件行业客户分类及销售人员激励
  10. LeetCode/LintCode 题解丨一周爆刷字符串:旋转字符数组