Skip to main content

动画

动画对于创造出色的用户体验非常重要。静止对象开始移动时必须克服惯性。运动中的对象具有动量,很少会立即停止。动画使你能够在界面中传达物理上可信的运动。

¥Animations are very important to create a great user experience. Stationary objects must overcome inertia as they start moving. Objects in motion have momentum and rarely come to a stop immediately. Animations allow you to convey physically believable motion in your interface.

React Native 提供了两个互补的动画系统:Animated 用于对特定值进行粒度和交互式控制,LayoutAnimation 用于动画全局布局事务。

¥React Native provides two complementary animation systems: Animated for granular and interactive control of specific values, and LayoutAnimation for animated global layout transactions.

Animated API

Animated API 旨在以非常高效的方式简洁地表达各种有趣的动画和交互模式。Animated 侧重于输入和输出之间的声明性关系,以及之间的可配置转换,以及 start/stop 方法来控制基于时间的动画执行。

¥The Animated API is designed to concisely express a wide variety of interesting animation and interaction patterns in a very performant way. Animated focuses on declarative relationships between inputs and outputs, with configurable transforms in between, and start/stop methods to control time-based animation execution.

Animated 导出六种可动画组件类型:ViewTextImageScrollViewFlatListSectionList,但你也可以使用 Animated.createAnimatedComponent() 创建自己的。

¥Animated exports six animatable component types: View, Text, Image, ScrollView, FlatList and SectionList, but you can also create your own using Animated.createAnimatedComponent().

例如,安装时淡入的容器视图可能如下所示:

¥For example, a container view that fades in when it is mounted may look like this:

让我们来分析一下这里发生了什么。在 FadeInView 构造函数中,一个名为 fadeAnim 的新 Animated.Value 被初始化为 state 的一部分。View 上的不透明度属性映射到该动画值。在幕后,提取数值并用于设置不透明度。

¥Let's break down what's happening here. In the FadeInView constructor, a new Animated.Value called fadeAnim is initialized as part of state. The opacity property on the View is mapped to this animated value. Behind the scenes, the numeric value is extracted and used to set opacity.

安装组件时,不透明度设置为 0。然后,在 fadeAnim 动画值上启动缓动动画,当该值动画到最终值 1 时,这将更新每个帧上的所有相关映射(在本例中仅更新不透明度)。

¥When the component mounts, the opacity is set to 0. Then, an easing animation is started on the fadeAnim animated value, which will update all of its dependent mappings (in this case, only the opacity) on each frame as the value animates to the final value of 1.

这是以比调用 setState 和重新渲染更快的优化方式完成的。由于整个配置是声明性的,因此我们将能够实现进一步的优化,序列化配置并在高优先级线程上运行动画。

¥This is done in an optimized way that is faster than calling setState and re-rendering. Because the entire configuration is declarative, we will be able to implement further optimizations that serialize the configuration and runs the animation on a high-priority thread.

配置动画

¥Configuring animations

动画是高度可配置的。自定义和预定义的缓动函数、延迟、持续时间、衰减因子、弹簧常量等都可以根据动画类型进行调整。

¥Animations are heavily configurable. Custom and predefined easing functions, delays, durations, decay factors, spring constants, and more can all be tweaked depending on the type of animation.

Animated 提供了几种动画类型,最常用的是 Animated.timing()。它支持使用各种预定义的缓动函数之一随时间对值进行动画处理,或者你也可以使用自己的函数。缓动函数通常在动画中使用来传达对象的逐渐加速和减速。

¥Animated provides several animation types, the most commonly used one being Animated.timing(). It supports animating a value over time using one of various predefined easing functions, or you can use your own. Easing functions are typically used in animation to convey gradual acceleration and deceleration of objects.

默认情况下,timing 将使用 easeInOut 曲线,该曲线将逐渐加速至全速,并以逐渐减速至停止结束。你可以通过传递 easing 参数来指定不同的缓动函数。还支持在动画开始之前自定义 duration 甚至 delay

¥By default, timing will use an easeInOut curve that conveys gradual acceleration to full speed and concludes by gradually decelerating to a stop. You can specify a different easing function by passing an easing parameter. Custom duration or even a delay before the animation starts is also supported.

例如,如果我们想要创建一个 2 秒长的对象动画,该动画在移动到最终位置之前会稍微后退:

¥For example, if we want to create a 2-second long animation of an object that slightly backs up before moving to its final position:

Animated.timing(this.state.xPosition, {
toValue: 100,
easing: Easing.back(),
duration: 2000,
useNativeDriver: true,
}).start();

查看 Animated API 参考的 配置动画 部分,了解有关内置动画支持的所有配置参数的更多信息。

¥Take a look at the Configuring animations section of the Animated API reference to learn more about all the config parameters supported by the built-in animations.

创作动画

¥Composing animations

动画可以组合并按顺序或并行播放。顺序动画可以在上一个动画完成后立即播放,也可以在指定的延迟后开始。Animated API 提供了多种方法,例如 sequence()delay(),每个方法都需要一组动画来执行,并根据需要自动调用 start()/stop()

¥Animations can be combined and played in sequence or in parallel. Sequential animations can play immediately after the previous animation has finished, or they can start after a specified delay. The Animated API provides several methods, such as sequence() and delay(), each of which take an array of animations to execute and automatically calls start()/stop() as needed.

例如,以下动画滑行停止,然后在平行旋转时弹回:

¥For example, the following animation coasts to a stop, then it springs back while twirling in parallel:

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,
useNativeDriver: true,
}),
Animated.parallel([
// after decay, in parallel:
Animated.spring(position, {
toValue: {x: 0, y: 0}, // return to start
useNativeDriver: true,
}),
Animated.timing(twirl, {
// and twirl
toValue: 360,
useNativeDriver: true,
}),
]),
]).start(); // start the sequence group

如果一个动画停止或中断,则该组中的所有其他动画也会停止。Animated.parallel 有一个 stopTogether 选项,可以将其设置为 false 以禁用此选项。

¥If one animation is stopped or interrupted, then all other animations in the group are also stopped. Animated.parallel has a stopTogether option that can be set to false to disable this.

你可以在 Animated API 参考的 创作动画 部分找到组合方法的完整列表。

¥You can find a full list of composition methods in the Composing animations section of the Animated API reference.

组合动画值

¥Combining animated values

你可以通过加法、乘法、除法或模数 组合两个动画值 来生成新的动画值。

¥You can combine two animated values via addition, multiplication, division, or modulo to make a new animated value.

在某些情况下,一个动画值需要反转另一个动画值进行计算。一个例子是反转比例 (2x --> 0.5x):

¥There are some cases where an animated value needs to invert another animated value for calculation. An example is inverting a scale (2x --> 0.5x):

const a = new Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
toValue: 2,
useNativeDriver: true,
}).start();

插值法

¥Interpolation

每个属性都可以首先通过插值运行。插值将输入范围映射到输出范围,通常使用线性插值,但也支持缓动函数。默认情况下,它会推断超出给定范围的曲线,但你也可以让它限制输出值。

¥Each property can be run through an interpolation first. An interpolation maps input ranges to output ranges, typically using a linear interpolation but also supports easing functions. By default, it will extrapolate the curve beyond the ranges given, but you can also have it clamp the output value.

将 0-1 范围转换为 0-100 范围的基本映射如下:

¥A basic mapping to convert a 0-1 range to a 0-100 range would be:

value.interpolate({
inputRange: [0, 1],
outputRange: [0, 100],
});

例如,你可能希望将 Animated.Value 想象为从 0 到 1,但将位置从 150px 动画到 0px,不透明度从 0 到 1。这可以通过修改上面示例中的 style 来完成,如下所示:

¥For example, you may want to think about your Animated.Value as going from 0 to 1, but animate the position from 150px to 0px and the opacity from 0 to 1. This can be done by modifying style from the example above like so:

  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, 你可以这样做:

¥interpolate() supports multiple range segments as well, which is handy for defining dead zones and other handy tricks. For example, to get a negation relationship at -300 that goes to 0 at -100, then back up to 1 at 0, and then back down to zero at 100 followed by a dead-zone that remains at 0 for everything beyond that, you could do:

value.interpolate({
inputRange: [-300, -100, 0, 100, 101],
outputRange: [300, 0, 1, 0, 0],
});

它将像这样映射:

¥Which would map like so:

Input | Output
------|-------
-400| 450
-300| 300
-200| 150
-100| 0
-50| 0.5
0| 1
50| 0.5
100| 0
101| 0
200| 0

interpolate() 还支持映射到字符串,允许你对颜色以及带有单位的值进行动画处理。例如,如果你想设置旋转动画,你可以这样做:

¥interpolate() also supports mapping to strings, allowing you to animate colors as well as values with units. For example, if you wanted to animate a rotation you could do:

value.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg'],
});

interpolate() 还支持任意缓动函数,其中许多函数已在 Easing 模块中实现。interpolate() 还具有用于推断 outputRange 的可配置行为。你可以通过设置 extrapolateextrapolateLeftextrapolateRight 选项来设置外推法。默认值为 extend,但你可以使用 clamp 来防止输出值超过 outputRange

¥interpolate() also supports arbitrary easing functions, many of which are already implemented in the Easing module. interpolate() also has configurable behavior for extrapolating the outputRange. You can set the extrapolation by setting the extrapolate, extrapolateLeft, or extrapolateRight options. The default value is extend but you can use clamp to prevent the output value from exceeding outputRange.

跟踪动态值

¥Tracking dynamic values

动画值还可以通过将动画的 toValue 设置为另一个动画值而不是普通数字来跟踪其他值。例如,像 Android 上 Messenger 使用的那样的 "聊天头" 动画可以通过固定在另一个动画值上的 spring() 来实现,或者通过 timing()duration 为 0 来实现刚性跟踪。它们也可以通过插值组成:

¥Animated values can also track other values by setting the toValue of an animation to another animated value instead of a plain number. For example, a "Chat Heads" animation like the one used by Messenger on Android could be implemented with a spring() pinned on another animated value, or with timing() and a duration of 0 for rigid tracking. They can also be composed with interpolations:

Animated.spring(follower, {toValue: leader}).start();
Animated.timing(opacity, {
toValue: pan.x.interpolate({
inputRange: [0, 300],
outputRange: [1, 0],
useNativeDriver: true,
}),
}).start();

leaderfollower 动画值将使用 Animated.ValueXY() 实现。ValueXY 是处理 2D 交互(例如平移或拖动)的便捷方法。它是一个基本封装器,包含两个 Animated.Value 实例和一些调用它们的辅助函数,使 ValueXY 在许多情况下成为 Value 的直接替代品。它允许我们跟踪上例中的 x 和 y 值。

¥The leader and follower animated values would be implemented using Animated.ValueXY(). ValueXY is a handy way to deal with 2D interactions, such as panning or dragging. It is a basic wrapper that contains two Animated.Value instances and some helper functions that call through to them, making ValueXY a drop-in replacement for Value in many cases. It allows us to track both x and y values in the example above.

追踪手势

¥Tracking gestures

手势(如平移或滚动)和其他事件可以使用 Animated.event 直接映射到动画值。这是通过结构化映射语法完成的,以便可以从复杂的事件对象中提取值。第一级是一个允许跨多个参数映射的数组,并且该数组包含嵌套对象。

¥Gestures, like panning or scrolling, and other events can map directly to animated values using Animated.event. This is done with a structured map syntax so that values can be extracted from complex event objects. The first level is an array to allow mapping across multiple args, and that array contains nested objects.

例如,在使用水平滚动手势时,你需要执行以下操作才能将 event.nativeEvent.contentOffset.x 映射到 scrollXAnimated.Value):

¥For example, when working with horizontal scrolling gestures, you would do the following in order to map event.nativeEvent.contentOffset.x to scrollX (an Animated.Value):

 onScroll={Animated.event(
// scrollX = e.nativeEvent.contentOffset.x
[{nativeEvent: {
contentOffset: {
x: scrollX
}
}
}]
)}

以下示例实现了水平滚动轮播,其中滚动位置指示器使用 ScrollView 中使用的 Animated.event 进行动画处理

¥The following example implements a horizontal scrolling carousel where the scroll position indicators are animated using the Animated.event used in the ScrollView

带有动画事件示例的 ScrollView

¥ScrollView with Animated Event Example

当使用 PanResponder 时,你可以使用以下代码从 gestureState.dxgestureState.dy 中提取 x 和 y 位置。我们在数组的第一个位置使用 null,因为我们只对传递给 PanResponder 处理程序的第二个参数感兴趣,即 gestureState

¥When using PanResponder, you could use the following code to extract the x and y positions from gestureState.dx and gestureState.dy. We use a null in the first position of the array, as we are only interested in the second argument passed to the PanResponder handler, which is the gestureState.

onPanResponderMove={Animated.event(
[null, // ignore the native event
// extract dx and dy from gestureState
// like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
{dx: pan.x, dy: pan.y}
])}

具有动画事件示例的 PanResponder

¥PanResponder with Animated Event Example

响应当前动画值

¥Responding to the current animation value

你可能会注意到,在制作动画时没有明确的方法来读取当前值。这是因为由于优化,该值可能仅在原生运行时中已知。如果你需要运行 JavaScript 来响应当前值,有两种方法:

¥You may notice that there is no clear way to read the current value while animating. This is because the value may only be known in the native runtime due to optimizations. If you need to run JavaScript in response to the current value, there are two approaches:

  • spring.stopAnimation(callback) 将停止动画并使用最终值调用 callback。这在进行手势转换时很有用。

    ¥spring.stopAnimation(callback) will stop the animation and invoke callback with the final value. This is useful when making gesture transitions.

  • spring.addListener(callback) 将在动画运行时异步调用 callback,提供最近的值。这对于触发状态更改非常有用,例如,当用户将其拖动得更近时,将小球捕捉到新选项,因为与需要在 60 下运行的连续手势(如平移)相比,这些较大的状态更改对几帧延迟不太敏感。 帧率。

    ¥spring.addListener(callback) will invoke callback asynchronously while the animation is running, providing a recent value. This is useful for triggering state changes, for example snapping a bobble to a new option as the user drags it closer, because these larger state changes are less sensitive to a few frames of lag compared to continuous gestures like panning which need to run at 60 fps.

Animated 被设计为完全可序列化,以便动画可以以高性能方式运行,独立于正常的 JavaScript 事件循环。这确实会影响 API,因此当与完全同步系统相比做某事似乎有点棘手时,请记住这一点。查看 Animated.Value.addListener 作为解决其中一些限制的方法,但请谨慎使用它,因为它可能会对将来的性能产生影响。

¥Animated is designed to be fully serializable so that animations can be run in a high performance way, independent of the normal JavaScript event loop. This does influence the API, so keep that in mind when it seems a little trickier to do something compared to a fully synchronous system. Check out Animated.Value.addListener as a way to work around some of these limitations, but use it sparingly since it might have performance implications in the future.

使用原生驱动程序

¥Using the native driver

Animated API 被设计为可序列化。通过使用 原生驱动程序,我们在开始动画之前将有关动画的所有内容发送到原生,从而允许原生代码在 UI 线程上执行动画,而无需在每一帧上都经过桥接器。一旦动画开始,就可以阻塞 JS 线程而不影响动画。

¥The Animated API is designed to be serializable. By using the native driver, we send everything about the animation to native before starting the animation, allowing native code to perform the animation on the UI thread without having to go through the bridge on every frame. Once the animation has started, the JS thread can be blocked without affecting the animation.

使用原生驱动程序进行普通动画可以通过在启动时在动画配置中设置 useNativeDriver: true 来完成。由于历史原因,没有 useNativeDriver 属性的动画将默认为 false,但会发出警告(以及 TypeScript 中的类型检查错误)。

¥Using the native driver for normal animations can be accomplished by setting useNativeDriver: true in animation config when starting it. Animations without a useNativeDriver property will default to false for legacy reasons, but emit a warning (and typechecking error in TypeScript).

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Set this to true
}).start();

动画值仅与一个驱动程序兼容,因此如果你在某个值上启动动画时使用原生驱动程序,请确保该值上的每个动画也使用原生驱动程序。

¥Animated values are only compatible with one driver so if you use native driver when starting an animation on a value, make sure every animation on that value also uses the native driver.

原生驱动程序也适用于 Animated.event。这对于跟随滚动位置的动画特别有用,因为没有原生驱动程序,由于 React Native 的异步特性,动画将始终在手势后面运行一帧。

¥The native driver also works with Animated.event. This is especially useful for animations that follow the scroll position as without the native driver, the animation will always run a frame behind the gesture due to the async nature of React Native.

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {y: this.state.animatedValue},
},
},
],
{useNativeDriver: true}, // <-- Set this to true
)}>
{content}
</Animated.ScrollView>

你可以通过运行 RNTester 应用,然后加载原生动画示例来查看原生驱动程序的运行情况。你还可以查看 源代码 以了解这些示例是如何生成的。

¥You can see the native driver in action by running the RNTester app, then loading the Native Animated Example. You can also take a look at the source code to learn how these examples were produced.

注意事项

¥Caveats

目前,原生驱动程序并不支持你使用 Animated 执行的所有操作。主要限制是你只能为非布局属性设置动画:像 transformopacity 这样的东西可以工作,但 Flexbox 和位置属性则不行。使用 Animated.event 时,它仅适用于直接事件,不适用于冒泡事件。这意味着它不适用于 PanResponder,但适用于 ScrollView#onScroll 之类的东西。

¥Not everything you can do with Animated is currently supported by the native driver. The main limitation is that you can only animate non-layout properties: things like transform and opacity will work, but Flexbox and position properties will not. When using Animated.event, it will only work with direct events and not bubbling events. This means it does not work with PanResponder but does work with things like ScrollView#onScroll.

当动画运行时,它可以阻止 VirtualizedList 组件渲染更多行。如果你需要在用户滚动列表时运行长动画或循环动画,你可以在动画配置中使用 isInteraction: false 来防止此问题。

¥When an animation is running, it can prevent VirtualizedList components from rendering more rows. If you need to run a long or looping animation while the user is scrolling through a list, you can use isInteraction: false in your animation's config to prevent this issue.

记住

¥Bear in mind

使用 rotateYrotateX 等变换样式时,请确保变换样式 perspective 就位。目前,如果没有它,某些动画可能无法在 Android 上渲染。下面的例子。

¥While using transform styles such as rotateY, rotateX, and others ensure the transform style perspective is in place. At this time some animations may not render on Android without it. Example below.

<Animated.View
style={{
transform: [
{scale: this.state.scale},
{rotateY: this.state.rotateY},
{perspective: 1000}, // without this line this Animation will not render on Android while working fine on iOS
],
}}
/>

其他示例

¥Additional examples

RNTester 应用有各种 Animated 的使用示例:

¥The RNTester app has various examples of Animated in use:

LayoutAnimation API

LayoutAnimation 允许你全局配置 createupdate 动画,这些动画将用于下一个渲染/布局周期中的所有视图。这对于进行 Flexbox 布局更新非常有用,无需费心测量或计算特定属性以便直接为它们设置动画,并且当布局更改可能影响祖级时尤其有用,例如 "查看更多" 扩展也会增加父级的大小并下推 下面的行否则需要组件之间的显式协调,以便使它们全部同步动画。

¥LayoutAnimation allows you to globally configure create and update animations that will be used for all views in the next render/layout cycle. This is useful for doing Flexbox layout updates without bothering to measure or calculate specific properties in order to animate them directly, and is especially useful when layout changes may affect ancestors, for example a "see more" expansion that also increases the size of the parent and pushes down the row below which would otherwise require explicit coordination between the components in order to animate them all in sync.

请注意,虽然 LayoutAnimation 非常强大并且非常有用,但它提供的控制比 Animated 和其他动画库要少得多,因此如果你无法让 LayoutAnimation 执行你想要的操作,你可能需要使用另一种方法。

¥Note that although LayoutAnimation is very powerful and can be quite useful, it provides much less control than Animated and other animation libraries, so you may need to use another approach if you can't get LayoutAnimation to do what you want.

请注意,为了使其在 Android 上运行,你需要通过 UIManager 设置以下标志:

¥Note that in order to get this to work on Android you need to set the following flags via UIManager:

UIManager.setLayoutAnimationEnabledExperimental(true);

本示例使用预设值,你可以根据需要自定义动画,更多信息请参见 LayoutAnimation.js

¥This example uses a preset value, you can customize the animations as you need, see LayoutAnimation.js for more information.

补充注意

¥Additional notes

requestAnimationFrame

requestAnimationFrame 是你可能熟悉的浏览器的填充代码。它接受一个函数作为其唯一参数,并在下一次重绘之前调用该函数。它是动画的重要构建块,是所有基于 JavaScript 的动画 API 的基础。一般来说,你不需要自己调用它 - 动画 API 将为你管理帧更新。

¥requestAnimationFrame is a polyfill from the browser that you might be familiar with. It accepts a function as its only argument and calls that function before the next repaint. It is an essential building block for animations that underlies all of the JavaScript-based animation APIs. In general, you shouldn't need to call this yourself - the animation APIs will manage frame updates for you.

setNativeProps

正如 在直接操作部分 中提到的,setNativeProps 允许我们直接修改原生支持的组件(实际上由原生视图支持的组件,与复合组件不同)的属性,而无需 setState 并重新渲染组件层次结构。

¥As mentioned in the Direct Manipulation section, setNativeProps allows us to modify properties of native-backed components (components that are actually backed by native views, unlike composite components) directly, without having to setState and re-render the component hierarchy.

我们可以在反弹示例中使用它来更新比例 - 如果我们要更新的组件是深层嵌套的并且尚未使用 shouldComponentUpdate 进行优化,这可能会有所帮助。

¥We could use this in the Rebound example to update the scale - this might be helpful if the component that we are updating is deeply nested and hasn't been optimized with shouldComponentUpdate.

如果你发现动画出现丢帧(每秒执行速度低于 60 帧),请考虑使用 setNativePropsshouldComponentUpdate 来优化它们。或者你可以在 UI 线程而不是 JavaScript 线程 使用 useNativeDriver 选项 上运行动画。你可能还希望使用 InteractionManager.X 将任何计算密集型工作推迟到动画完成之后。你可以使用应用内开发菜单 "帧率监视器" 工具监控帧速率。

¥If you find your animations with dropping frames (performing below 60 frames per second), look into using setNativeProps or shouldComponentUpdate to optimize them. Or you could run the animations on the UI thread rather than the JavaScript thread with the useNativeDriver option. You may also want to defer any computationally intensive work until after animations are complete, using the InteractionManager. You can monitor the frame rate by using the In-App Dev Menu "FPS Monitor" tool.