在不使用任何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.ViewAnimated.TextAnimated.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入门 —— 动画篇(上)的更多相关文章

  1. ReactNative入门 —— 动画篇(下)

    在上篇动画入门文章中我们了解了在 React Native 中简单的动画的实现方式,本篇将作为上篇的延续,介绍如何使用 Animated 实现一些比较复杂的动画. 动画组合 在 Animated 中提 ...

  2. jQuery-4.动画篇---上卷下拉效果

    jQuery中下拉动画slideDown 对于隐藏的元素,在将其显示出来的过程中,可以对其进行一些变化的动画效果.之前学过了show方法,show方法在显示的过程中也可以有动画,但是.show()方法 ...

  3. ReactNative入门(安卓)——API(上)

    Alert - 弹窗 通过 Alert.alert() 方法调用唤起原生弹窗,点击会触发 onPress 回调(参考下方代码)并清除弹窗. import React, { AppRegistry, C ...

  4. Apache Maven 入门篇 ( 上 )

    作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法. 这个入门篇分上下两篇.本文着重动手,用 mav ...

  5. React-Native入门指导之iOS篇 —— 一、准备工作

    React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...

  6. [转]Apache Maven 入门篇 ( 上 )

    原文地址:Apache Maven 入门篇 ( 上 ) 作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这 ...

  7. React-Native入门指导之iOS篇

    React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...

  8. iOS React-Native入门指南之HelloWorld

    React-native 作为facebook开源项目,最近是火的一塌糊涂,它采用node.js能够写ios和android的native界面代码,简直是太酷了.支持动态更新,而且appstore 提 ...

  9. [转] ReactNative Animated动画详解

    http://web.jobbole.com/84962/     首页 所有文章 JavaScript HTML5 CSS 基础技术 前端职场 工具资源 更多频道▼ - 导航条 - 首页 所有文章 ...

随机推荐

  1. 前端框架 EasyUI (2)页面布局 Layout

    在 Web 程序中,页面布局对应用程序的用户体验至关重要. 在一般的信息管理类的 Web 应用程序中,页面结构通常有一个主工作区,然后在工作区上下左右靠近边界的区域设置一些边栏,用于显示信息或放置一些 ...

  2. 移动硬盘不能识别的常见7种解决方案 ~ By 逆天经验

    服务器汇总:http://www.cnblogs.com/dunitian/p/4822808.html#iis 服务器异常: http://www.cnblogs.com/dunitian/p/45 ...

  3. 多线程爬坑之路-Thread和Runable源码解析

    多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...

  4. 【架构设计】分布式文件系统 FastDFS的原理和安装使用

    本文地址 分享提纲: 1.概述 2. 原理 3. 安装 4. 使用 5. 参考文档 1. 概述 1.1)[常见文件系统] Google了一下,流行的开源分布式文件系统有很多,介绍如下:   -- mo ...

  5. CSS三个定位——常规、浮动、绝对定位

    .dage { width: 868px; background: #5B8C75; border: 10px solid #A08C5A; margin-top: -125px; margin-le ...

  6. 原生JavaScript实现hasClass、addClass、removeClass、toggleClass

    兼容IE6+,因IE6.IE7.IE8不支持Array.prototype.indexOf()和String.prototype.trim(),分别用Polyfill实现支持. 详细: indexOf ...

  7. LVM基本介绍与常用命令

    一.LVM介绍LVM是 Logical Volume Manager(逻辑卷管理)的简写,它是Linux环境下对磁盘分区进行管理的一种机制LVM - 优点:LVM通常用于装备大量磁盘的系统,但它同样适 ...

  8. 解决Ubuntu Kylin 1610安装ANSYS17.2的NVIDIA显卡驱动问题

    Ubuntu Kylin 1610在安装完毕后,会自动安装显卡驱动,对于一般的图形图像使用来说自然不会有太大的问题,但是对于ANSYS17.2的一些模块,还是会出现问题.一个比较常见的问题就是Open ...

  9. Linux学习日记-使用EF6 Code First(四)

    一.在linux上使用EF 开发环境 VS2013+mono 3.10.0 +EF 6.1.0 先检测一下EF是不是6的 如果不是  请参阅 Linux学习日记-EF6的安装升级(三) 由于我的数据库 ...

  10. 分布式存储 FastDFS-5.0.5线上搭建

    前言:       由于公司项目需要,最近开始学习一下分布式存储相关知识,确定使用FastDFS这个开源工具.学习之初,自己利用VMware虚拟机搭建了一个5台机器的集群,摸清了安装过程中可能出现的问 ...