React Native 动画入门

以下是你在实现 React Native 动画时克服性能挑战所需的工具。
376 位读者喜欢这篇文章。
Woman programming

WOCinTech Chat. Opensource.com 修改. CC BY-SA 4.0

React Native 动画是研讨会和课程的热门话题,可能是因为许多开发人员发现它具有挑战性。 虽然许多在线博客和资源都关注 React Native 的性能方面,但很少有人带您了解基础知识。 在本文中,我将讨论如何实现 React Native 动画的基础知识。

首先,让我们回顾一些背景和历史。

历史和发展

为了使用跨平台的 JavaScript 代码,您手机的本机组件必须通过一个名为“桥”的元素来传递信息。“桥”是异步的,这会导致基于 JavaScript 的 React Native 应用程序滞后,因为通过“桥”的异步请求会阻塞 JavaScript 代码与本机部分交互的路径。

为了实现高性能,动画必须在本机 UI 线程上渲染。 由于数据需要在“桥”上进行序列化,这通常会阻塞 JavaScript 线程,从而导致帧丢失。 自 2015 年以来,这个问题一直很普遍,当时动画是 React Native 中最大的限制之一。

幸运的是,由于社区成员的大力支持,情况已经有所改善。 现在,React Native 动画通常可以实现每秒 60 帧 (FPS)。 诸如 Animated 之类的声明式 API 简化了交互式动画的实现过程。

使用 Animated API 提高性能

尽管如此,开发人员遇到 性能问题 并不罕见,尤其是在处理复杂动画时。

如上所述,React Native 动画中的性能瓶颈是由 JavaScript 线程上的繁重工作负载引起的,这会降低帧速率并导致用户体验缓慢。 要克服这个问题,您需要保持每秒 60 帧的帧速率。

使用 Animated API 是最好的解决方案,因为它可以优化所需的序列化/反序列化时间。 它通过使用声明式 API来描述动画来实现这一点。 其背后的想法是提前一次性声明整个动画,以便可以在 JavaScript 中序列化该声明并将其发送到“桥”。 然后,驱动程序逐帧执行动画。

如何在 React Native 中实现动画

在 React Native 中实现动画的方法有几种。 以下是我发现最有用的方法。

动画值

动画值在我的列表中名列前茅,它是任何 React Native 应用程序中动画的构建块。 它们通常指向一个真实值,该值在与动画组件一起传递时会转换回实数。

让我们看一个例子

Animated.timing(this.valueToAnimate, {
    toValue: 42;
    duration: 1000;
}).start()

在上面的例子中,我声明了 value.ToAnimate 为 42,它将在 1000 毫秒后执行。

您还可以使用动画值来声明诸如不透明度或位置之类的属性。 这是使用动画值实现不透明度的示例

<Animated.View style={{ opacity: myAnimatedOpacity }} />  

动画驱动程序:Animated.timing, Animated.event, Animated.decay

可以将驱动程序视为图中每个帧都会更改动画值的节点。 例如,Animated.timing 将增加一个值,而 Animated.decay 将在每个帧上减少一个值。

让我们看一个例子

Animated.decay(this.valueToAnimate, {
   velocity: 2.0,
   deceleration: 0.9
}).start();

此示例以特定速度启动动画,并在特定时间逐渐减速。 我喜欢在材料设计最初出现时在跨平台应用程序中执行此操作。 感觉很棒,并且有很多方法可以使动画传递令人难忘的体验。

您还可以使用 Animated.event 来驱动用户滚动的值

<ScrollView onScroll={Animated.event(
  [{nativeEvent: {contentOffset: {y: this.state.scrollY}}}]
)}
>
</ScrollView>

在上面的例子中,Animated.event 返回一个函数,该函数将 scrollViewnativeEvent.contentOffset.y 设置为当前的 scrollY 状态。

总而言之,动画驱动程序可以与动画值或其他动画驱动程序一起使用。

顺便说一句,请记住,当驱动程序更新每个帧时,新值将立即更新 View 属性。 因此,在声明变量时要小心,并在使用变量时注意其范围。

转换方法

转换方法使您可以将动画值转换为新的动画值。

您可以使用 Animated.add()Animated.multiply()Animated.interpolate() 等方法来实现转换操作。 您可以使用它在 Animated 图中的任何节点上执行转换操作

newAnimated.Value(55).interpolate(.....) // Transformation operation using Animated.interpolate() method

动画属性

动画属性是特殊节点,可将动画值映射到组件上的属性。 当您渲染 Animated.view 并为其分配属性时,会生成它。

让我们看一下下面的代码片段

Var opacity = new Animated.Value(0.7);
<Animated.View style={{ opacity }} />

在这里,我添加了一个动画属性,该属性将值 0.7 转换为属性。 如果某个方法更新了该值,则该更改将反映在 View 的属性中。

上述方法协同工作并在 React Native 中动画对象中发挥着至关重要的作用。

动画的每一帧的动画值都由动画驱动程序(Animated.TimingAnimated.EventAnimated.Decay)更改。 然后将结果传递给任何转换操作,在其中将其存储为视图的属性(不透明度或转换值)。

然后,JavaScript 将结果传递到本机领域,在其中通过调用 setNativeProps 来更新视图。 最后,将其传递到 iOS 或 Android,在其中更新 UIView 或 Android.View。

使用 Animated API 和 Native Driver 实现动画

自从 React Native Animated API 问世以来,JavaScript 驱动程序一直用于帧执行,但这会导致帧丢失,因为业务逻辑直接落在 JavaScript 线程上。

request_animation_frame.png

为了解决帧丢失问题,最新版本的驱动程序被完全设为本机,并且现在能够在本机领域中逐帧执行动画。

与 Animated API 一起使用时,Native Driver 允许本机动画模块直接更新视图,而无需在 JavaScript 中计算值。

为了使用 Native Driver,您必须在配置动画时指定 useNativeDrivertrue

useNativeDriver: true

使用 PanResponder 处理 React Native 中的手势

React Native Animated API 可以在实现 React Native 动画时为您完成大部分“繁重的工作”。 但是,在动画中实现手势方面,它有一个关键限制:它无法响应超出 ScrollView 范围的手势。

虽然您可以使用简单的 ScrollView 组件完成很多事情,但您可能会同意,没有手势的移动设备是不完整的,手势是用户使用动画执行的操作,例如滚动或平移。

在 React Native 中,可以通过将 PanResponder 与 Animated API 一起使用来无缝处理手势。

PanResponder 将各种触摸组合成一个特定的手势。 它使单次触摸能够响应额外的触摸,从而使手势能够顺利运行。

默认情况下,PanResponder 包含一个 InteractionManager 句柄,该句柄会阻止在 JS 线程上运行的事件中断手势。

提高慢速 Navigator 过渡的正常运行时间

任何涉及从一个应用程序屏幕移动到另一个应用程序屏幕的 React Native 动画通常应使用导航组件完成。 诸如 React Navigation 之类的导航组件通常用于导航过渡。

在 React Native 中,导航过渡通常发生在 JavaScript 线程中,这可能导致低端/低内存设备(通常是 Android 设备,因为 iOS 设备可以更有效地处理这些设备)的过渡缓慢。 当 React Native 尝试渲染新屏幕时,导航过渡缓慢通常会发生,而动画仍在后台执行。

为了避免这种情况,InteractionManager 允许在动画或交互已在 JavaScript 线程中执行后安排长时间运行的任务。

布局动画

LayoutAnimation 是一个简单的 API,可在下一次布局发生时自动将视图动画到下一个连续位置。 它在 UI 线程上运行,这使其具有很高的性能。

使用 LayoutAnimation 配置的动画将在调用后应用于所有组件,这与 Animated 不同,在 Animated 中,您可以控制要动画的特定值。 LayoutAnimation 能够动画显示下一次渲染时发生的所有更改,因此您应该在调用 setState 之前调用它。

在调用 setState 之前配置布局动画将确保在本机线程中进行流畅的动画处理,并防止您的动画受到另一个 setState diff 中的代码触发的影响(在正常情况下,这会损害应用程序的动画)。

使用 LayoutAnimation 的另一种方法是在组件的 WillReceiveProps 方法中调用它。 只需通过传递适当的动画配置参数来调用 LayoutAnimation.configureNext,如下所示

LayoutAnimation.configureNext(animationConfiguration, callbackCompletionMethod); 
this.setState({ stateToChange: newStateValue }); 

LayoutAnimation 仅支持两个属性:不透明度和可伸缩性。

它通过使用其唯一键并计算其预期位置来识别视图。 此外,只要视图在状态更改之间保持相同的键,它就会动画显示帧更改。

使用 LayoutAnimation 实现的动画是本机发生的,这从性能角度来看是件好事,但是如果需要在状态之间动画所有属性,则可能会带来挑战。

结束语

本文仅触及 React Native 动画的表面。 在 React Native 中工作的一个经验法则是尽可能使用 Animated API。 对于手势,请将 PanResponder 与 Animated API 一起使用。

将 Native Driver 与 Animated API 一起使用将消除您可能遇到的大多数问题,但是如果性能仍然滞后,请坚持使用 LayoutAnimation。

User profile image.
开源框架和摇滚乐是驱动 Rakshit 的动力。 他的另一半帮助初创公司和组织采用技术。 在 Twitter 上联系他 @RakshitSoral。

2 条评论

Creative Commons License本作品采用 Creative Commons Attribution-Share Alike 4.0 International License 授权。
© . All rights reserved.