react native学习笔记29——动画篇 Animated高级动画
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.View
、Animated.Text
、Animated.Image
和Animated.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.View
、Animated.Text
、Animated.Image
和Animated.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/speed
或tension/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.spring
、Animated.delay
、Animated.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.dx
和gestureState.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高级动画相关推荐
- React Native学习笔记一之搭建开发环境
因为项目需要,今天开始正式学习React Native,先来搭建个开发环境 忐忑的心情 因为项目比较急,而且客户要求使用React Native开发,只能先学点基础然后在项目中使用的时候,边做边学了, ...
- react native学习笔记13——FlatList上拉加载
我们可以利用官方组件RefreshControl实现下拉刷新功能,但React Native官方没有提供相应的上拉加载的组件,因此在RN中实现上拉加载比下拉刷新要复杂一点. 虽然没有直接提供上拉加载的 ...
- react native 学习笔记
编译第一坑 Error type 3. Activity class {com.awesome_project/ com.awesome_project.MainActivity} does not ...
- React Native学习笔记-1:JSC profiler is not supported.(转载)
运行react-native中Example下的UIEXPLORER Project 遇到虾面报错: 2016-03-21 14:12:18.941 [trace][tid:com.facebook. ...
- React Native 学习笔记六(关于宽高的设置)
继续在之前的例子上进行添加 尺寸 1.使用固定的尺寸 设置View容器 和设置自定义的组组件 如果父组件的空间不足 自控件的会出现重叠的情况 示例: import {AppRegistry,S ...
- React Native 学习资源精选仓库
<React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开源App.资源下载.以及相关新闻等,只求精不求全.因后面无法 Pu ...
- React Native 学习资源精选仓库(汇聚知识,分享精华)
React Native 学习资源精选仓库(汇聚知识,分享精华) <React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开 ...
- JavaScript学习笔记之入门篇
JavaScript学习笔记之入门篇 JavaScript引入 1. 页面级 js: 2. 外部js文件: JavaScript变量 1. 变量的作用: 2. 声明变量: 3. 变量赋值: 4. 单一 ...
- React Native学习提纲
React Native学习提纲 当前版本最后修订日期: 2015年10月21日 一. React.js入门基础 1.基础HTML/CSS与基础开发工具使用 - html基础 doctype.常用标签 ...
最新文章
- Oracle SQL高级编程——分析函数(窗口函数)全面讲解
- java中setid(),Java Process.setId方法代碼示例
- linux运维有前途么,想去做linux运维,不知道有前途么?
- 关于 Nuxt 集成ueditor的一些坑(包括图片上传)前端部分
- [vue-element] 你有二次封装过ElementUI组件吗?
- 【软件开发底层知识修炼】七 Binutils辅助工具之- ar工具与nm工具
- 编译 linux 3,linux内核的编译(3)
- 2018年最值得关注的15大技术趋势
- C语言中字符串的处理方式(一)
- pytorch1.0神经网络保存、提取、加载
- zabbix--从入门到精通之zabbix历史数据
- linux ubuntu内核安装位置,在Ubuntu中安装或升级内核 linux kernel
- 计算机信应用技术,计算机信息应用技术.ppt
- console application
- 如何让计算机桌面字体变大,如何把字体放大 如何更改桌面与网页字体大小
- 等级保护三级信息系统安全设计
- Dell电脑重装系统
- 服务(Service)
- App集成ApplePay
- Selenium启动项参数设置
热门文章
- 群晖 Drive 的团队文件夹显示 “无法取得“
- android 8华为屏幕录制,数码知识:华为nova8pro如何屏幕录制怎么录屏
- Adobe Premiere Pro CC 2018 剪裁音频文件攻略
- php增加vip等级设置,会员管理系统中商家如何设置会员等级
- 三菱PLC模板程序FX5U轴FB块 使用ST语言编写的轴FB块,包含原点复归,点动,定位运动
- Shader Graph学习(一)
- *** Cisco路由器
- 计算机二级柏林是第几套,2019年计算机二级Office考试内容及时间安排(内附两套原题库)...
- 穆穆推荐-软件销售行业软件公司销售参考操作手册-之5-软件行业客户分类及销售人员激励
- LeetCode/LintCode 题解丨一周爆刷字符串:旋转字符数组