动画
流畅、有意义的动画对于移动应用用户体验来说是非常重要的。现实生活中的物体在开始移动和停下来的时候都具有一定的惯性,我们在界面中也可以使用动画来实现契合物理规律的交互。
React Native 提供了两个互补的动画系统:用于创建精细的交互控制的动画Animated
和用于全局的布局动画LayoutAnimation
。
Animated
Animated
使得开发者可以简洁地实现各种各样的动画和交互方式,并且具备极高的性能。Animated
旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用start/stop
方法来控制动画按顺序执行。 Animated
仅封装了 6 个可以动画化的组件:View
、Text
、Image
、ScrollView
、FlatList
和SectionList
,不过你也可以使用Animated.createAnimatedComponent()
来封装你自己的组件。下面是一个在加载时带有淡入动画效果的视图:
我们来分解一下这个过程。在FadeInView
的构造函数里,我们创建了一个名为fadeAnim
的Animated.Value
,并放在state
中。而View
的透明度是和这个值绑定的。
组件加载时,透明度首先设为 0。然后一个 easing 动画开始改变fadeAnim
值,同时会导致所有与其相关联的值(本例中是透明度)也逐帧更新,最终和fadeAnim
一样变为 1。
这一过程经过特别优化,执行效率会远高于反复调用setState
和多次重渲染。
因为这一过程是纯声明式的,因此还有进一步优化的空间,比如我们可以把这些声明的配置序列化后发送到一个高优先级的线程上运行。
配置动画
动画拥有非常灵活的配置项。自定义的或预定义的 easing 函数、延迟、持续时间、衰减系数、弹性常数等都可以在对应类型的动画中进行配置。
Animated
提供了多种动画类型,其中最常用的要属Animated.timing()
。它可以使用一些预设的easing
曲线函数来控制动画值的变化速度,也支持自定义的曲线函数。动画中通常使用easing
曲线函数来控制物体的加速或减速变化。
默认情况下timing
使用easeInOut
曲线,它使动画体逐渐加速到最大然后逐渐减速到停止。你可以通过传递easing
参数来指定不 同的变化速度,还支持自定义duration
持续时间,甚至是动画开始前的delay
延迟。
下面这个例子创建了一个 2 秒长的动画,在移动目标到最终位置前会稍微往后退一点:
Animated.timing(this.state.xPosition, {
toValue: 100,
easing: Easing.back(),
duration: 2000
}).start();
如果想了解更多配置参数,请参阅Animated
文档的配置动画章节。
组合动画
多个动画可以通过parallel
(同时执行)、sequence
(顺序执行)、stagger
和delay
来组合使用。它们中的每一个都接受一个要执行的动画数组,并且自动在适当的时候调用start/stop
。
例如,以下的动画滑行停止,然后在平行旋转的同时弹回:
Animated.sequence([
// decay, then spring to start and twirl
Animated.decay(position, {
// coast to a stop
velocity: { x: gestureState.vx, y: gestureState.vy }, // velocity from gesture release
deceleration: 0.997
}),
Animated.parallel([
// after decay, in parallel:
Animated.spring(position, {
toValue: { x: 0, y: 0 } // return to start
}),
Animated.timing(twirl, {
// and twirl
toValue: 360
})
])
]).start(); // start the sequence group
默认情况下,如果任何一个动画被停止或中断了,组内所有其它的动画也会被停止。Parallel 有一 个stopTogether
属性,如果设置为false
,可以禁用自动停止。
在Animated
文档的组合动画一节中列出了所有的组合方法。
合成动画值
你可以使用加减乘除以及取余等运算来把两个动画值合成为一个新的动画值。
有些时候,一些动画值需要依赖另一些值来做计算。比如下面的例子,以一个动画值为分母,增大其值以实现合成值的缩小(1x --> 0.5x)
const a = new Animated.Value(1);
const b = Animated.divide(1, a);
Animated.spring(a, {
toValue: 2
}).start();
插值
所有动画值都可以执行插值(interpolation)操作。插值是指将一定范围的输入值映射到另一组不同的输出值,一般我们使用线性的映射,但是也可以使用 easing 函数。默认情况下,它会将曲线外推到给定范围之外,但您也可以让它限制为输出值。
一个简单的将范围 0-1 转换为范围 0-100 的映射操作是:
value.interpolate({
inputRange: [0, 1],
outputRange: [0, 100]
});
例如,你可能想通过使用 Animated.Value
的值从 0 变化到 1 来让 position
从 150px 变化到 0px,同时 opacity
从 0 变为 1。这一点可以通过将 style
从 example 修改为下面的样子来实现:
style={{
opacity: this.state.fadeAnim, // Binds directly
transform: [{
translateY: this.state.fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [150, 0] // 0 : 150, 0.5 : 75, 1 : 0
}),
}],
}}
interpolate()
还支持定义多个区间段落,常用来定义静止区间等。举个例子,要让输入在接近 -300 时取相反值,然后在输入接近 -100 时到达 0,然后在输入接近 0 时又回到 1,接着一直到输入到 100 的过程中逐步回到 0,最后形成一个始终为 0 的静止区间,对于任何大于 100 的输入都返回 0。具体写法如下:
value.interpolate({
inputRange: [-300, -100, 0, 100, 101],
outputRange: [300, 0, 1, 0, 0]
});
它的最终映射结果如下:
| 输入 | 输出 |
| ---- | ---- |
| -400 | 450 |
| -300 | 300 |
| -200 | 150 |
| -100 | 0 |
| -50 | 0.5 |
| 0 | 1 |
| 50 | 0.5 |
| 100 | 0 |
| 101 | 0 |
| 200 | 0 |
interpolate()
还支持到字符串的映射,从而可以实现颜色以及带有单位的值的动画变换。例如你可以像下面这样实现一个旋转动画:
value.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg']
});
interpolate()
还支持任意的渐变函数,其中有很多已经在Easing
类中定义了,包括二次、指数、贝塞尔等曲线以及 step、bounce 等方法。interpolation
还支持限制输出区间outputRange
。你可以通过设置extrapolate
、extrapolateLeft
或extrapolateRight
属性来限制输出区间。默认值是extend
(允许超出),不过你可以使用clamp
选项来阻止输出值超过outputRange
。