在移动开发中,动画能有效的提高用户体验。在 React Native 中,也有相应的 API 供我们做动画。这里着重说一下 Animated 动画库,它可以让我们非常简便的去实现各式各样的动画和交互方式,而且具备很高的性能。Animated 目前只封装了四个可以动画化的组件:View、Text、Image、ScrollView,不过你也可以用 Animated.createAnimatedComponent() 来封装你自己的组件。
话不多说,我们来举个栗子:
步骤拆解
1、创建 Animated.Value,设置初始值,比如一个 View 组件的透明度,最开始设置 fadeAnim:Animated.Value(0) 来表示动画开始的时候,是透明的。
2、把创建的这个 Animated.Value 绑定到 Style 的动画属性,例:<View style={{opacity:this.state.fadeAnim}}></View>
3、使用 Animated.timing (还有其他的)来创建自动的动画,或者使用 Animated.event 来根据手势,触摸,Scroll 的动态更新动画的状态。
4、调用 Animated.timing.start() 开始动画, start 接受一个回调函数作为参数,将会在执行了动画之后执行回调函数。
动画模式
如果只是 timing ,肯定是无法满足我们复杂的交互效果的需求的,所以 RN 还给我们提供了另外两种动画模式。
1、spring 弹簧效果
friction 摩擦系数,默认为40
tension 张力系数,默认为7
bounciness
speed
2、decay 衰变效果
velocity 出速率
deceleration 衰减系数,默认为0.997
spring 支持 friction 与 tension 或者 bounciness 与 speed 两种组合模式,这两种模式不能并存。其中 friction 与 tension 模型来源于 Origami ,一款F家自制的动画原型设计工具,而 bounciness 与 speed 则是传统的弹簧模型参数。
栗子不够复杂?
看来一个简单的淡入是无法满足大家的好奇心的,我们整个大点的。
在上面的例子里面,我们实现的是三个动画效果同时进行,因为我们给文字区域加上了字体增大的动画效果,相应地,也要修改 Text 为 Animated.Text
强大的插值 interpolate
相信大家都已经注意到了,我们上面用到了 interpolate 这个函数,也就是插值函数。这个函数很强大,当我们动画的值与要改变的属性值不是同一单位的时候,就可以使用 interpolate 来帮我们进行一个单位的映射转换,比如
当 rotation 这个动画状态的值为0时,那么输出的Z轴上的旋转角度会自动映射成0deg。当 rotation 这个动画状态的值为0.5时,那么输出的Z轴上的旋转角度会自动映射成180deg。以此类推。 InputRange 并不局限于 [0,1] 区间,这个主要取决于你定义的动画的初始值,和想要变化以后的值,并且其间还可以存在多段映射。插值的好处在于我们可以声明一个动画变量来控制多个并行动画,简单易控制。
Interpolate 支持多段区间映射, [0,1] 区间和 [1,2] 区间之间没有什么必然联系,当 rotation 趋近于1时,动画旋转趋近于360deg,当 rotation 趋近于2时,动画也可以旋转回来趋近于200deg,唯一要注意的就是 inputRange 的每一个值都必须有一个 outputRange 里面的值与其对应。
流程控制
在刚才的栗子中,我们使用了 parallel 来实现多个动画的并行渲染,其他用于流程控制的 API 还有:
1、sequence 接受一系列动画数组为参数,并依次执行
2、stagger 接受一系列动画数组和一个延迟时间,按照序列,每隔一个延迟时间后执行下一个动画(其实就是插入了 delay 的 parallel )
3、delay 生成一个延迟时间(默认值为0,单位为毫秒)
第二个栗子稍微修改一下,就可以根据业务逻辑去控制自己的动画流程,在上面的栗子里面,我们让动画首先出现,出现了之后,再同时进行字体变大和旋转两个动画,虽然他们持续的时间和到达的值不一样,但是他们是在 opacity 变为1以后同时开始的。
需要注意的点
可以看到我们上面的动画都是以毫秒级的频率来执行的,也就相当于我们会以毫秒级的频率去调用 setState,而每次调用 setState 都会重新调用 render 方法遍历子元素进行渲染,就算有 dom diff 帮我们算,他也会累的(负担不起这么大的计算量和 UI 渲染量)。这里浅谈几个优化方案,具体收益就只有大家在实际项目中自己体会了。
使用原生驱动
这算是官方给出的一个比较简单的解决方案了,在动画中启用原生驱动非常简单。只需在开始动画之前,在动画配置中加入一行 useNativeDriver:true 。
ShouldComponentUpdate
大家都知道,ShouldComponentUpdate 是性能优化利器,只需要在子组件的 ShouldComponentUpdate 返回 false,就可以省去很多多余渲染花费。这样做也有一个弊端,毕竟我们的子元素并不是一成不变的,这样粗暴的直接返回 false 的话,会让子元素变成一滩死水,所以使用的时候请权衡利弊。
SetNativeProps (局部刷新)
可能这都不算是动画的的优化方案,但是却可以直接改动组件并触发局部的刷新。使用这个方法修改 View、Text 等 RN 自带的组件,就不会触发组件的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate 等组件生命周期中的方法。
LayoutAnimation
这也是官方给的一个比较简单的动画解决方案,它允许我们在全局范围内创建和更新动画,这些动画会在下一次渲染或布局周期运行。