http://web.jobbole.com/84962/

 
 

- 导航条 -
首页
所有文章
JavaScript
HTML5
CSS
基础技术
前端职场
工具资源
更多频道▼
- iOS
- Java
- Android
- Python

 
 
伯乐在线 > WEB前端 - 伯乐在线 > 所有文章 > JavaScript > ReactNative Animated动画详解
 

ReactNative Animated动画详解

2016/01/26 · JavaScript · React

分享到:3
原文出处: AlloyTeam   

最近ReactNative(以下简称RN)在前端的热度越来越高,不少同学开始在业务中尝试使用RN,这里着重介绍一下RN中动画的使用与实现原理。

使用篇

举个简单的栗子

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var React = require('react-native');
var {
    Animated,
    Easing,
    View,
    StyleSheet,
    Text
} = React;
 
var Demo = React.createClass({
    getInitialState() {
        return {
            fadeInOpacity: new Animated.Value(0) // 初始值
        };
    },
    componentDidMount() {
        Animated.timing(this.state.fadeInOpacity, {
            toValue: 1, // 目标值
            duration: 2500, // 动画时间
            easing: Easing.linear // 缓动函数
        }).start();
    },
    render() {
        return (
            <Animated.View style={[styles.demo, {
                    opacity: this.state.fadeInOpacity
                }]}>
                <Text style={styles.text}>悄悄的,我出现了</Text>
            </Animated.View>
        );
    }
});
 
var styles = StyleSheet.create({
    demo: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'white',
    },
    text: {
        fontSize: 30
    }
});

是不是很简单易懂<(▰˘◡˘▰)> 和JQuery的Animation用法很类似。

步骤拆解

一个RN的动画,可以按照以下步骤进行。

  1. 使用基本的Animated组件,如Animated.View Animated.Image Animated.Text (重要!不加Animated的后果就是一个看不懂的报错,然后查半天动画参数,最后怀疑人生
  2. 使用Animated.Value设定一个或多个初始化值(透明度,位置等等)。
  3. 将初始化值绑定到动画目标的属性上(如style)
  4. 通过Animated.timing等函数设定动画参数
  5. 调用start启动动画。

栗子敢再复杂一点吗?

显然,一个简单的渐显是无法满足各位观众老爷们的好奇心的.我们试一试加上多个动画

 
 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
getInitialState() {
    return (
        fadeInOpacity: new Animated.Value(0),
            rotation: new Animated.Value(0),
            fontSize: new Animated.Value(0)
    );
},
componentDidMount() {
    var timing = Animated.timing;
    Animated.parallel(['fadeInOpacity', 'rotation', 'fontSize'].map(property => {
                return timing(this.state[property], {
                toValue: 1,
                duration: 1000,
                easing: Easing.linear
            });
        })).start();
},
render() {
    return (<Animated.View style={[styles.demo, {
            opacity: this.state.fadeInOpacity,
                transform: [{
                    rotateZ: this.state.rotation.interpolate({
                        inputRange: [0,1],
                        outputRange: ['0deg', '360deg']
                    })
                }]
            }]}><Animated.Text style={{
                fontSize: this.state.fontSize.interpolate({
                    inputRange: [0,1],
                    outputRange: [12,26]
                })
            }}>我骑着七彩祥云出现了

注意到我们给文字区域加上了字体增大的动画效果,相应地,也要修改Text为Animated.Text

强大的interpolate

上面的栗子使用了interpolate函数,也就是插值函数。这个函数很强大,实现了数值大小、单位的映射转换,比如

 
 
 
 
 

JavaScript

 
1
2
3
4
{  
    inputRange: [0,1],
    outPutRange: ['0deg','180deg']
}

当setValue(0.5)时,会自动映射成90deg。 inputRange并不局限于[0,1]区间,可以画出多段。 interpolate一般用于多个动画共用一个Animated.Value,只需要在每个属性里面映射好对应的值,就可以用一个变量控制多个动画。 事实上,上例中的fadeInOpacityfontSizerotation用一个变量来声明就可以了。(那你写那么多变量逗我吗(╯‵□′)╯︵┻━┻) (因为我要强行使用parallel ┬─┬ ノ( ‘ – ‘ノ))

流程控制

在刚才的栗子中,我们使用了Parallel来实现多个动画的并行渲染,其它用于流程控制的API还有:

  • sequence接受一系列动画数组为参数,并依次执行
  • stagger接受一系列动画数组和一个延迟时间,按照序列,每隔一个延迟时间后执行下一个动画(其实就是插入了delay的parrllel)
  • delay生成一个延迟时间(基于timing的delay参数生成)

例3

 
 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
getInitialState() {
    return (
        anim: [1,2,3].map(() => new Animated.Value(0)) // 初始化3个值
    );
},
 
componentDidMount() {
    var timing = Animated.timing;
    Animated.sequence([
        Animated.stagger(200, this.state.anim.map(left => {
            return timing(left, {
                toValue: 1,
              });
            }).concat(
                this.state.anim.map(left => {
                    return timing(left, {
                        toValue: 0,
                    });
                })
            )), // 三个view滚到右边再还原,每个动作间隔200ms
            Animated.delay(400), // 延迟400ms,配合sequence使用
            timing(this.state.anim[0], {
                toValue: 1
            }),
            timing(this.state.anim[1], {
                toValue: -1
            }),
            timing(this.state.anim[2], {
                toValue: 0.5
            }),
            Animated.delay(400),
            Animated.parallel(this.state.anim.map((anim) => timing(anim, {
                toValue: 0
            }))) // 同时回到原位置
        ]
    ).start();
},
render() {
    var views = this.state.anim.map(function(value, i) {
        return (
            <Animated.View
                key={i}
                style={[styles.demo, styles['demo' + i], {
                    left: value.interpolate({
                        inputRange: [0,1],
                        outputRange: [0,200]
                    })
                }]}>
                <Text style={styles.text}>我是第{i + 1}个View</Text>
 
            </Animated.View>
        );
    });
    return <View style={styles.container}>
               <Text>sequence/delay/stagger/parallel演示</Text>
               {views}
           </View>;
}

Spring/Decay/Timing

前面的几个动画都是基于时间实现的,事实上,在日常的手势操作中,基于时间的动画往往难以满足复杂的交互动画。对此,RN还提供了另外两种动画模式。

  • Spring 弹簧效果

    • friction 摩擦系数,默认40
    • tension 张力系数,默认7
    • bounciness
    • speed
  • Decay 衰变效果
    • velocity 初速率
    • deceleration 衰减系数 默认0.997

Spring支持 friction与tension 或者 bounciness与speed 两种组合模式,这两种模式不能并存。 其中friction与tension模型来源于origami,一款F家自制的动画原型设计工具,而bounciness与speed则是传统的弹簧模型参数。

Track && Event

RN动画支持跟踪功能,这也是日常交互中很常见的需求,比如跟踪用户的手势变化,跟踪另一个动画。而跟踪的用法也很简单,只需要指定toValue到另一个Animated.Value就可以了。 交互动画需要跟踪用户的手势操作,Animated也很贴心地提供了事件接口的封装,示例:

 
 
 
 
 
 

JavaScript

 
1
2
3
4
// Animated.event 封装手势事件等值映射到对应的Animated.Value
onPanResponderMove: Animated.event(
    [null, {dx: this.state.x, dy: this.state.y}] // map gesture to leader
)

在官方的demo上改了一下,加了一张费玉污的图,效果图如下 代码太长,就不贴出来了,可以参考官方Github代码

动画循环

Animated的start方法是支持回调函数的,在动画或某个流程结束的时候执行,这样子就可以很简单地实现循环动画了。

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
startAnimation() {
    this.state.rotateValue.setValue(0);
    Animated.timing(this.state.rotateValue, {
        toValue: 1,
        duration: 800,
        easing: Easing.linear
    }).start(() => this.startAnimation());
}

是不是很魔性?[doge]

原理篇

首先感谢能看到这里的小伙伴们:)

在上面的文章中,我们已经基本掌握了RN Animated的各种常用API,接下来我们来了解一下这些API是如何设计出来的。

声明: 以下内容参考自Animated原作者的分享视频

首先,从React的生命周期来编程的话,一个动画大概是这样子写:

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getInitialState() {
    return {left: 0};
}
 
render(){
    return (
        <div style={{left: this.state.left}}>
            <Child />
        </div>
    );
}
 
onChange(value) {
    this.setState({left: value});
}

只需要通过requestAnimationFrame调用onChange,输入对应的value,动画就简单粗暴地跑起来了。◕‿◕,全剧终。

然而事实总是没那么简单,问题在哪?

我们看到,上述动画基本是以毫秒级的频率在调用setState,而React的每次setState都会重新调用render方法,并切遍历子元素进行渲染,即使有Dom Diff也可能扛不住这么大的计算量和UI渲染。

那么该如何优化呢?

  • 关键词:

    • ShouldComponentUpdate
    • <StaticContainer>(静态容器)
    • Element Caching (元素缓存)
    • Raw DOM Mutation (原生DOM操作)
    • ↑↑↓↓←→←→BA (秘籍)

ShouldComponentUpdate

学过React的都知道,ShouldComponentUpdate是性能优化利器,只需要在子组件的shouldComponentUpdate返回false,分分钟渲染性能爆表。

然而并非所有的子元素都是一成不变的,粗暴地返回false的话子元素就变成一滩死水了。而且组件间应该是独立的,子组件很可能是其他人写的,父元素不能依赖于子元素的实现。

<StaticContainer>(静态容器)

这时候可以考虑封装一个容器,管理ShouldCompontUpdate,如图示:

小明和老王再也不用关心父元素的动画实现啦。

一个简单的\<StaticContainer\>实现如下:

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class StaticContainer extends React.Component {
    render(){
        return this.props.children;
    }
    shouldComponentUpdate(nextProps){
        return nextProps.shouldUpdate; // 父元素控制是否更新
    }
}
 
// 父元素嵌入StaticContainer
render() {
    return (
        <div style={{left: this.state.left}}>
            <StaticContainer
            shouldUpdate={!this.state.isAnimating}>
                <ExpensiveChild />
            </StaticContainer>
        </div>
    );
}

Element Caching 缓存元素

还有另一种思路优化子元素的渲染,那就是缓存子元素的渲染结果到局地变量。

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
render(){
    this._child = this._child || <ExpensiveChild />;
    return (
        <div style={{left:this.state.left}}>
            {this._child}
        </div>
    );
}

缓存之后,每次setState时,React通过DOM Diff就不再渲染子元素了。

上面的方法都有弊端,就是条件竞争。当动画在进行的时候,子元素恰好获得了新的state,而这时候动画无视了这个更新,最后就会导致状态不一致,或者动画结束的时候子元素发生了闪动,这些都是影响用户操作的问题。

Raw DOM Mutation 原生DOM操作

刚刚都是在React的生命周期里实现动画,事实上,我们只想要变更这个元素的left值,并不希望各种重新渲染、DOM DIFF等等发生。

“React,我知道自己要干啥,你一边凉快去“

如果我们跳出这个生命周期,直接找到元素进行变更,是不是更简单呢?

简单易懂,性能彪悍,有木有?!

然而弊端也很明显,比如这个组件unmount之后,动画就报错了。

Uncaught Exception: Cannot call ‘style’ of null

而且这种方法照样避不开条件竞争——动画值改变的时候,有可能发生setState之后,left又回到初始值之类的情况。

再者,我们使用React,就是因为不想去关心dom元素的操作,而是交给React管理,直接使用Dom操作显然违背了初衷。

↑↑↓↓←→←→BA (秘籍)

唠叨了这么多,这也不行,那也不行,什么才是真理?

我们既想要原生DOM操作的高性能,又想要React完善的生命周期管理,如何把两者优势结合到一起呢?答案就是Data Binding(数据绑定)

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
render(){
    return(
        <Animated.div style={{left: this.state.left}}>
             <ExpensiveChild />
        </Animated.div>
    );
}
 
getInitialState(){
    return {left: new Animated.Value(0)}; // 实现了数据绑定的类
}
 
onUpdate(value){
    this.state.left.setValue(value); // 不是setState
}

首先,需要实现一个具有数据绑定功能的类Animated.Value,提供setValueonChange等接口。 其次,由于原生的组件并不能识别Value,需要将动画元素用Animated包裹起来,在内部处理数据变更与DOM操作。

一个简单的动画组件实现如下:

 
 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Animated.div = class extends React.Component{
    componentWillUnmount() {
        nextProps.style.left.removeAllListeners();
    },
    // componentWillMount需要完成与componentWillReceiveProps同样的操作,此处略
    componentWillReceiveProps(nextProps) {
        nextProps.style.left.removeAllListeners();
        nextProps.style.left.onChange(value => {
            React.findDOMNode(this).style.left = value + 'px';
        });
 
        // 将动画值解析为普通数值传给原生div
        this._props = React.addons.update(
            nextProps,
            {style:{left:{$set: nextProps.style.left.getValue()}}}
        );
    },
    render() {
        return <div ...{this._props} />;
    }
}

代码很简短,做的事情有:

  1. 遍历传入的props,查找是否有Animated.Value的实例,并绑定相应的DOM操作。
  2. 每次props变更或者组件unmount的时候,停止监听数据绑定事件,避免了条件竞争和内存泄露问题。
  3. 将初始传入的Animated.Value值逐个转化为普通数值,再交给原生的React组件进行渲染。

综上,通过封装一个Animated的元素,内部通过数据绑定和DOM操作变更元素,结合React的生命周期完善内存管理,解决条件竞争问题,对外表现则与原生组件相同,实现了高效流畅的动画效果。

读到这里,应该知道为什么ImageText等做动画一定要使用Animated加持过的元素了吧?

[转] ReactNative Animated动画详解的更多相关文章

  1. [转]Animation 动画详解(一)——alpha、scale、translate、rotate、set的xml属性及用法

    转载:http://blog.csdn.net/harvic880925/article/details/39996643 前言:这几天做客户回访,感触很大,用户只要是留反馈信息,总是一种恨铁不成钢的 ...

  2. [转]超级强大的SVG SMIL animation动画详解

    超级强大的SVG SMIL animation动画详解 本文花费精力惊人,具有先驱前瞻性,转载规则以及申明见文末,当心予以追究.本文地址:http://www.zhangxinxu.com/wordp ...

  3. Android中的动画详解系列【4】——Activity之间切换动画

    前面介绍了Android中的逐帧动画和补间动画,并实现了简单的自定义动画,这一篇我们来看看如何将Android中的动画运用到实际开发中的一个场景--Activity之间跳转动画. 一.定义动画资源 如 ...

  4. Android Animation动画详解(二): 组合动画特效

    前言 上一篇博客Android Animation动画详解(一): 补间动画 我已经为大家介绍了Android补间动画的四种形式,相信读过该博客的兄弟们一起都了解了.如果你还不了解,那点链接过去研读一 ...

  5. css 12-CSS3属性详解:动画详解

    12-CSS3属性详解:动画详解 #前言 本文主要内容: 过渡:transition 2D 转换 transform 3D 转换 transform 动画:animation #过渡:transiti ...

  6. Android 三种动画详解

    [工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.点我开始Android技术交流] 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让 ...

  7. 超级强大的SVG SMIL animation动画详解

    本文花费精力惊人,具有先驱前瞻性,转载规则以及申明见文末,当心予以追究.本文地址:http://www.zhangxinxu.com/wordpress/?p=4333 //zxx: 本文的SVG在有 ...

  8. css变换与动画详解

    举个栗子:--------元素整体居中.box{     position:absolute;top:50%;left:50%;    width:50px;    height:50px;    t ...

  9. JQuery动画详解(四)

    一:基本动画show()显示隐藏的匹配元素.这个就是 'show( speed, [callback] )' 无动画的版本.如果选择的元素是可见的,这个方法将不会改变任何东西.无论这个元素是通过hid ...

随机推荐

  1. iOS: 填充数据表格

    功能:创建一个列表并填充 // // main.m // Hello // // Created by lishujun on 14-8-28. // Copyright (c) 2014年 lish ...

  2. [CF Round #294 div2] E. A and B and Lecture Rooms 【树上倍增】

    题目链接:E. A and B and Lecture Rooms 题目大意 给定一颗节点数10^5的树,有10^5个询问,每次询问树上到xi, yi这两个点距离相等的点有多少个. 题目分析 若 x= ...

  3. 实现pushViewController:animated:的不同页面转换特效

    1. 首先要明确的是,不使用pushViewController的默认动画,所以在调用这个函数时,要将animated设置为NO.2. 使用普通的来CATransition实现转换效果,代码如下:CA ...

  4. 应用安全技术趋势之 Top 5

    而今,大多数应用都依赖于像入侵防护系统(Instrusion Prevention System)和 Web 应用防火墙(Web Application Firewall,以下全文简称 WAF)这样的 ...

  5. Cow Marathon

    poj1985:http://poj.org/problem?id=1985 题意:就是树的直径. 题解:直接DFS即可. #include<iostream> #include<c ...

  6. web版本控制

    说说你在web开发中是怎么进行版本管理的 在web开发中,我首先将系统框架搭建完成后,我会发布到服务器上,然后给小组成员进行分工,为他们划分各自的模块,他们每天早上上班时就从服务器上先将自己机器上面的 ...

  7. Node.js权威指南 (10) - Node.js中的错误处理与断言处理

    10.1 使用domain模块处理错误 / 272 10.1.1 domain模块概述 / 272 10.1.2 创建并使用Domain对象 / 274 10.1.3 隐式绑定与显式绑定 / 276 ...

  8. C# 客服端上传文件与服务器器端接收 (简单代码)

    简单代码: /*服务器端接收写入 可以实现断点续传*/ public string ConnectUpload(string newfilename,string filepath,byte[] fi ...

  9. mac下的改装人生——关于机械键盘

    这几天好像弄了很多关于机械键盘的东西,我自己的这块键盘也已经慢慢熟悉了,感觉打字超级爽哈,然后看了很多网上关于机械键盘的帖子,也看了很多教程,在Amazon和Taobao看了很多键盘的价位,前几天还试 ...

  10. openStack 瓶颈测试