ReactNative入门 —— 动画篇(上)
在不使用任何RN动画相关API的时候,我们会想到一种非常粗暴的方式来实现我们希望的动画效果——通过修改state来不断得改变视图上的样式。
我们来个简单的示例:
var AwesomeProject = React.createClass({
getInitialState() {
return { w: 200, h: 20 }
}, _onPress() {
//每按一次增加近30宽高
var count = 0;
while(++count<30){
requestAnimationFrame(()=>{
this.setState({w: this.state.w + 1, h: this.state.h + 1})
})
}
} render: function() {
return (
<View style={styles.container}>
<View style={[styles.content, {width: this.state.w, height: this.state.h}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
效果如下:
这种方式实现的动画存在两大问题:
1. 将频繁地销毁、重绘视图来实现动画效果,性能体验很糟糕,常规表现为内存花销大且动画卡顿明显;
2. 动画较为生硬,毕竟web的css3不适用在RN上,无法轻易设定动画方式(比如ease-in、ease-out)。
因此在RN上设置动画,还是得乖乖使用相应的API来实现,它们都能很好地触达组件底层的动画特性,以原生的形式来实现动画效果。
LayoutAnimation
LayoutAnimation 是在布局发生变化时触发动画的接口(我们在上一篇文章里已经介绍过),这意味着你需要通过修改布局(比如修改了组件的style、插入新组件)来触发动画。
该接口最常用的方法是 LayoutAnimation.configureNext(conf<Object>) ,用来设置下次布局变化时的动画效果,需要在执行 setState 前调用。
其中 conf 参数格式参考:
{
duration: 700, //持续时间
create: { //若是新布局的动画类型
type: 'linear', //线性模式
property: 'opacity' //动画属性,除了opacity还有一个scaleXY可以配置
},
update: { //若是布局更新的动画类型
type: 'spring', //弹跳模式
springDamping: 0.4 //弹跳阻尼系数
}
}
其中动画type的类型可枚举为:
spring //弹跳
linear //线性
easeInEaseOut //缓入缓出
easeIn //缓入
easeOut //缓出
keyboard //键入
要注意的是,安卓平台使用 LayoutAnimation 动画必须加上这么一句代码(否则动画会失效):
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
于是我们一开始的动画可以这么来写:
var AwesomeProject = React.createClass({
getInitialState() {
return { w: 200, h: 20 }
}, _onPress() {
LayoutAnimation.configureNext({
duration: 700, //持续时间
create: {
type: 'linear',
property: 'opacity'
},
update: {
type: 'spring',
springDamping: 0.4
}
});
this.setState({w: this.state.w + 30, h: this.state.h + 30})
} render: function() {
return (
<View style={styles.container}>
<View style={[styles.content, {width: this.state.w, height: this.state.h}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
这时候动画灵活和流畅多了:
ok我们上例看到的仅仅是布局更新的情况,我们来看看新布局被创建(有新组件加入到视图上)的情况如何:
var AwesomeProject = React.createClass({
getInitialState() {
return {
showNewOne : false,
w: 200,
h: 20
}
}, _onPress() {
LayoutAnimation.configureNext({
duration: 1200,
create: {
type: 'linear',
property: 'opacity' //注意这里,我们设置新布局被创建时的动画特性为透明度
},
update: {
type: 'spring',
springDamping: 0.4
}
});
this.setState({w: this.state.w + 30, h: this.state.h + 30, showNewOne : true})
},
render: function() {
var newOne = this.state.showNewOne ? (
<View style={[styles.content, {width: this.state.w, height: this.state.h}]}>
<Text style={[{textAlign: 'center'}]}>new one</Text>
</View>
) : null;
return (
<View style={styles.container}>
<View style={[styles.content, {width: this.state.w, height: this.state.h}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</View>
{newOne}
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
效果如下:
setNativeProps
如果我们执意使用开篇的修改state的方式,觉得这种方式更符合当前需求对动画的控制,那么则应当使用原生组件的 setNativeProps 方法来做对应实现,它会直接修改组件底层特性,不会重绘组件,因此性能也远胜动态修改组件内联style的方法。
该接口属原生组件(比如View,比如TouchableOpacity)才拥有的原生特性接口,调用格式参考如下:
Component.setNativeProps({
style: {transform: [{rotate:'50deg'}]}
});
对于开篇的动画示例,我们可以做如下修改:
var _s = {
w: 200,
h: 20
};
var AwesomeProject = React.createClass({
_onPress() {
var count = 0;
while(count++<30){
requestAnimationFrame(()=>{
this.refs.view.setNativeProps({
style: {
width : ++_s.w,
height : ++_s.h
}
});
})
}
}, render: function() {
return (
<View style={styles.container}>
<View ref="view" style={[styles.content, {width: 200, height: 20}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
效果如下(比开篇的示例流畅多了):
Animated
本文的重点介绍对象,通过 Animated 我们可以在确保性能良好的前提下创造更为灵活丰富且易维护的动画。
不同于上述的动画实现方案,我们得在 Animated.View、Animated.Text 或 Animated.Image 动画组件上运用 Animate 模块的动画能力(如果有在其它组件上的需求,可以使用 Animated.createAnimatedComponent
方法来对其它类型的组件创建动画)。
我们先来个简单的例子:
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
Easing,
Animated,
TouchableOpacity,
} = React; var _animateHandler;
var AwesomeProject = React.createClass({
componentDidMount() {
_animateHandler = Animated.timing(this.state.opacityAnmValue, {
toValue: 1, //透明度动画最终值
duration: 3000, //动画时长3000毫秒
easing: Easing.bezier(0.15, 0.73, 0.37, 1.2) //缓动函数
})
}, getInitialState() {
return {
opacityAnmValue : new Animated.Value(0) //设置透明度动画初始值
}
}, _onPress() {
_animateHandler.start && _animateHandler.start()
} render: function() {
return (
<View style={styles.container}>
<Animated.View ref="view" style={[styles.content, {width: 200, height: 20, opacity: this.state.opacityAnmValue}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</Animated.View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity >
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
}); var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
content: {
justifyContent: 'center',
backgroundColor: 'yellow',
},
button: {
marginTop: 10,
paddingVertical: 10,
paddingHorizontal: 20,
backgroundColor: 'black'
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
}
});
点击按钮后,Animated.View 会以bezier曲线形式执行时长3秒的透明度动画(由0到1):
so 我们做了这些事情:
1. 以 new Animated.Value(0) 实例化动画的初始值给state:
getInitialState() {
return {
opacityAnmValue : new Animated.Value(0) //设置透明度动画初始值
}
}
2. 通过 Animated.timing 我们定义了一个动画事件,在后续可以以 .start() 或 .stop() 方法来开始/停止该动画:
componentDidMount() {
_animateHandler = Animated.timing(this.state.opacityAnmValue, {
toValue: 1, //透明度动画最终值
duration: 3000, //动画时长3000毫秒
easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
})
},
我们在按钮点击事件中触发了动画的 .start 方法让它跑起来:
_onPress() {
_animateHandler.start && _animateHandler.start()
},
start 方法接受一个回调函数,会在动画结束时触发,并传入一个参数 {finished: true/false},若动画是正常结束的,finished 字段值为true,若动画是因为被调用 .stop() 方法而提前结束的,则 finished 字段值为false。
3. 动画的绑定是在 <Animate.View /> 上的,我们把实例化的动画初始值传入 style 中:
<Animated.View ref="view" style={[styles.content, {width: 200, height: 20, opacity: this.state.opacityAnmValue}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</Animated.View>
然后。。。没有然后了,这实在太简单了
这里需要讲一下的应该是定义动画事件的 Animated.timing(animateValue, conf<Object>) 方法,其中设置参数格式为:
{
duration: 动画持续的时间(单位是毫秒),默认为500。
easing:一个用于定义曲线的渐变函数。阅读Easing模块可以找到许多预定义的函数。iOS默认为Easing.inOut(Easing.ease)。
delay: 在一段时间之后开始动画(单位是毫秒),默认为0。
}
这里提及的 Easing 动画函数模块在 react-native/Libraries/Animated/src/ 目录下,该模块预置了 linear、ease、elastic、bezier 等诸多缓动特性,有兴趣可以去了解。
另外除了 Animated.timing,Animated 还提供了另外两个动画事件创建接口:
1. Animated.spring(animateValue, conf<Object>) —— 基础的单次弹跳物理模型,支持origami标准,conf参考:
{
friction: 控制“弹跳系数”、夸张系数,默认为7。
tension: 控制速度,默认40。
}
代码参考:
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
Easing,
Animated,
TouchableOpacity,
} = React; var _animateHandler;
var AwesomeProject = React.createClass({
componentDidMount() {
this.state.bounceValue.setValue(1.5); // 设置一个较大的初始值
_animateHandler = Animated.spring(this.state.bounceValue, {
toValue: 1,
friction: 8,
tension: 35
})
}, getInitialState() {
return {
bounceValue : new Animated.Value(0) //设置缩放动画初始值
}
}, _onPress() {
_animateHandler.start && _animateHandler.start()
},
_reload() {
AppRegistry.reload()
}, render: function() {
return (
<View style={styles.container}>
<Animated.View ref="view" style={[styles.content, {width: 200, height: 20, transform: [{scale: this.state.bounceValue}]}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</Animated.View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={this._reload}>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
}); var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
content: {
justifyContent: 'center',
backgroundColor: 'yellow',
},
button: {
marginTop: 10,
paddingVertical: 10,
paddingHorizontal: 20,
backgroundColor: 'black'
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
}
});
留意这里我们用了 animateValue.setValue(1.5) 方法来修改动画属性值。效果如下:
2. Animated.decay(animateValue, conf<Object>) —— 以一个初始速度开始并且逐渐减慢停止,conf参考:
{
velocity: 起始速度,必填参数。
deceleration: 速度衰减比例,默认为0.997。
}
代码参考:
var _animateHandler;
var AwesomeProject = React.createClass({
componentDidMount() {
_animateHandler = Animated.decay(this.state.bounceValue, {
toValue: 0.2,
velocity: 0.1
})
}, getInitialState() {
return {
bounceValue : new Animated.Value(0.1)
}
}, _onPress() {
_animateHandler.start && _animateHandler.start()
}, render: function() {
return (
<View style={styles.container}>
<Animated.View ref="view" style={[styles.content, {width: 200, height: 30, transform: [{scale: this.state.bounceValue}]}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</Animated.View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
对于最后介绍的两个动画效果,可能得熟悉一些物理、数学模型才能更好地来做控制,大部分情况下,咱们直接使用 Animated.timing 就足够满足需求了。
监听动画
1. 有时候我们需要在动画的过程中监听到某动画时刻的属性值,可以通过 animateValue.stopAnimation(callback<Function>) 或 animateValue.addListener(callback<Function>) 来实现
其中 stopAnimation 会停止当前动画并在回调函数中返回一个 {value : number} 对象,value对应最后一刻的动画属性值:
var _animateHandler,
_isFirsPress = 0;
var AwesomeProject = React.createClass({
componentDidMount() {
_animateHandler = Animated.timing(this.state.opacityAnmValue, {
toValue: 1,
duration: 6000,
easing: Easing.linear
})
}, getInitialState() {
return {
opacityAnmValue : new Animated.Value(0) //设置透明度动画初始值
}
}, _onPress() { if(_isFirsPress == 0){
_animateHandler.start && _animateHandler.start();
_isFirsPress = 1
}
else {
this.state.opacityAnmValue.stopAnimation(value => {
Alert.alert(
'动画结束,最终值:',
JSON.stringify(value),
[
{text: 'OK', onPress: () => {}}
]
)
})
}
}, render: function() {
return (
<View style={styles.container}>
<Animated.View style={[styles.content, {width: 200, height: 20, opacity: this.state.opacityAnmValue}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</Animated.View>
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
<TouchableOpacity >
<View style={styles.button}>
<Text style={styles.buttonText}>忽略本按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
而 addListener 方法会在动画的执行过程中持续异步调用callback回调函数,提供一个最近的值作为参数。
2. 有时候我们希望在某个交互事件(特别是手势)中更灵活地捕获某个事件对象属性值,并动态赋予某个变量,对于这种需求可以通过 Animated.event 来实现。
它接受一个数组为参数,数组中的层次对应绑定事件参数的相应映射,听着有点绕,看例子就很好理解了:
var scrollX = 0,
pan = {
x: 0,
y: 0
};
//...
onScroll : Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}] // scrollX = e.nativeEvent.contentOffset.x
)
onPanResponderMove : Animated.event([
null, // 忽略原生事件
{dx: pan.x, dy: pan.y} // 从gestureState中解析出dx和dy的值
]);
onScroll 是绑定给某个组件的滚动事件,而 onPanResponderMove 是 PanResponder 模块下的响应事件。
拿上方 onPanResponderMove 的例子来讲,该事件方法接收两个参数 e<event Object> 和 gestureState<Object>,其中 gestureState 的属性有:
stateID - 触摸状态的ID。在屏幕上有至少一个触摸点的情况下,这个ID会一直有效。
moveX - 最近一次移动时的屏幕横坐标
moveY - 最近一次移动时的屏幕纵坐标
x0 - 当响应器产生时的屏幕坐标
y0 - 当响应器产生时的屏幕坐标
dx - 从触摸操作开始时的累计横向路程
dy - 从触摸操作开始时的累计纵向路程
vx - 当前的横向移动速度
vy - 当前的纵向移动速度
numberActiveTouches - 当前在屏幕上的有效触摸点的数量
此处不了解的可以去看我上一篇RN入门文章的相关介绍。
而上方例子中,我们动态地将 gestureState.dx 和 gestureState.dy 的值赋予 pan.x 和 pan.y。
来个有简单的例子:
class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
transY : new Animated.Value(0)
};
this._panResponder = {}
}
componentWillMount处预先创建手势响应器
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._returnTrue.bind(this),
onMoveShouldSetPanResponder: this._returnTrue.bind(this),
//手势开始处理
//手势移动时的处理
onPanResponderMove: Animated.event([null, {
dy : this.state.transY
}])
});
} _returnTrue(e, gestureState) {
return true;
} render() {
return (
<View style={styles.container}>
<Animated.View style={[styles.content, {width: this.state.w, height: this.state.h,
transform: [{
translateY : this.state.transY
}]
}]}>
<Text style={[{textAlign: 'center'}]}>Hi, here is VaJoy</Text>
</Animated.View>
<TouchableOpacity>
<View style={styles.button} {...this._panResponder.panHandlers}>
<Text style={styles.buttonText}>control</Text>
</View>
</TouchableOpacity> <TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}>忽略此按钮</Text>
</View>
</TouchableOpacity>
</View>
);
}
}
动画的API较多,本章先介绍到这里,下篇将介绍更复杂的动画实现~ 共勉~
ReactNative入门 —— 动画篇(上)的更多相关文章
- ReactNative入门 —— 动画篇(下)
在上篇动画入门文章中我们了解了在 React Native 中简单的动画的实现方式,本篇将作为上篇的延续,介绍如何使用 Animated 实现一些比较复杂的动画. 动画组合 在 Animated 中提 ...
- jQuery-4.动画篇---上卷下拉效果
jQuery中下拉动画slideDown 对于隐藏的元素,在将其显示出来的过程中,可以对其进行一些变化的动画效果.之前学过了show方法,show方法在显示的过程中也可以有动画,但是.show()方法 ...
- ReactNative入门(安卓)——API(上)
Alert - 弹窗 通过 Alert.alert() 方法调用唤起原生弹窗,点击会触发 onPress 回调(参考下方代码)并清除弹窗. import React, { AppRegistry, C ...
- Apache Maven 入门篇 ( 上 )
作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法. 这个入门篇分上下两篇.本文着重动手,用 mav ...
- React-Native入门指导之iOS篇 —— 一、准备工作
React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...
- [转]Apache Maven 入门篇 ( 上 )
原文地址:Apache Maven 入门篇 ( 上 ) 作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这 ...
- React-Native入门指导之iOS篇
React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...
- iOS React-Native入门指南之HelloWorld
React-native 作为facebook开源项目,最近是火的一塌糊涂,它采用node.js能够写ios和android的native界面代码,简直是太酷了.支持动态更新,而且appstore 提 ...
- [转] ReactNative Animated动画详解
http://web.jobbole.com/84962/ 首页 所有文章 JavaScript HTML5 CSS 基础技术 前端职场 工具资源 更多频道▼ - 导航条 - 首页 所有文章 ...
随机推荐
- ASP.NET Core 1.1 简介
ASP.NET Core 1.1 于2016年11月16日发布.这个版本包括许多伟大的新功能以及许多错误修复和一般的增强.这个版本包含了多个新的中间件组件.针对Windows的WebListener服 ...
- 使用etree.HTML的编码问题
title: 使用etree.HTML的编码问题 date: 2015-10-07 17:56:47 categories: [Python] tags: [Python, lxml, Xpath] ...
- 基于netty http协议栈的轻量级流程控制组件的实现
今儿个是冬至,所谓“冬大过年”,公司也应景五点钟就放大伙儿回家吃饺子喝羊肉汤了,而我本着极高的职业素养依然坚持留在公司(实则因为没饺子吃没羊肉汤喝,只能呆公司吃食堂……).趁着这一个多小时的时间,想跟 ...
- 初探Vue
Vue.js(读音/vju:/,类似于view),是近来比较火的前端框架,但一直没有怎么具体了解.实现过,就知道个啥的MVVM啦,数据驱动啦,等这些关于Vue的虚概念. 由于最近,小生在公司中,负责开 ...
- [C#] C# 知识回顾 - 异常介绍
异常介绍 我们平时在写程序时,无意中(或技术不够),而导致程序运行时出现意外(或异常),对于这个问题, C# 有专门的异常处理程序. 异常处理所涉及到的关键字有 try.catch 和 finally ...
- C#中如何创建PDF网格并插入图片
这篇文章我将向大家演示如何以编程的方式在PDF文档中创建一个网格,并将图片插入特定的网格中. 网上有一些类似的解决方法,在这里我选择了一个免费版的PDF组件.安装控件后,创建新项目,添加安装目录下的d ...
- Loadrunner Http Json接口压力测试
前天接到了一个测试任务,要求测试一下ES(elsticsearch)在不同并发下的查询效率.如图: 业务场景是在客户端根据具体车牌查询相关车辆信息,结果返回前10条记录. 从图中可以看到,接口的请求参 ...
- const let,console.log('a',a)跟console.log('a'+a)的区别
const 创建一个只读的常量 let块级作用域 const let重复赋值都会报错 console.log('a',a) a console.log('a'+a) a2 逗号的值会有空格:用加号的值 ...
- Oracle常用SQL函数整理
--返回ASCII码select ASCII('A') "A的ASCII码" ,ASCII('a') "a的ASSCII码" from dual ; --反向 ...
- Pramp mock interview (4th practice): Matrix Spiral Print
March 16, 2016 Problem statement:Given a 2D array (matrix) named M, print all items of M in a spiral ...