1. 项目背景与核心需求解析
1.1 为什么要在鸿蒙上重新审视LayoutAnimation
先聊聊这套方案的来龙去脉。我们团队从去年开始把一套基于React Native的跨端业务逐步迁移到鸿蒙生态,目标很直接——在尽量不动业务逻辑的前提下,把RN容器跑在鸿蒙设备上。迁移过程中,最让我头疼的不是业务组件迁移,反而是那些平时在Android/iOS上“无感”的动画效果。尤其是LayoutAnimation,它在鸿蒙这一侧的表现和配置方式,跟传统RN平台有着明显的差异。
LayoutAnimation在RN里其实是个“老面孔”了。它和Animated最大的不同在于,Animated需要你手动声明每个动画值、驱动每个属性,而LayoutAnimation是系统级的布局动画——当你改变组件的尺寸、位置、添加或删除节点时,系统会自动为这些布局变化生成过渡动画。简单类比的话,Animated像是一个一个手动控制木偶的线,而LayoutAnimation更像是给木偶装上了惯性轮,你改变它的位置,它自己会滑过去。
但在鸿蒙平台上,LayoutAnimation的适配并不仅仅是“换个底层API”那么简单。鸿蒙的原生布局引擎、渲染管线和RN的架构映射关系,决定了LayoutAnimation在鸿蒙上从“触发条件”到“参数生效范围”都和传统平台存在差异。我们当时遇到的第一道坎就是:配置了enterLayoutAnimation和exitLayoutAnimation,但页面里的列表项增删完全没有任何动画效果,就像配置了个寂寞。所以这篇文章我不会只讲“LayoutAnimation怎么配弹簧动画”,而是把从“为什么在鸿蒙上配置不生效”到“如何正确配置并调出有质感的弹簧效果”这条完整链路,一步步拆给你看。
1.2 我们这套方案的实际应用场景
这个项目里,我们做的不是那种“为了动画而动画”的炫技页面,而是一个对交互反馈要求很高的业务场景——消息列表的加载、展开与删除。需求方要求三个核心体验点:列表项插入时要有“弹入”的轻快感;删除时要有“缩合退出”的连贯性;列表整体重排时不能有卡顿和跳变。直接用Animated逐个控制的话,代码会变得非常啰嗦,而且每个item的动画时序协调起来很容易出问题。LayoutAnimation天然就是干这个的。
我建议你在读这篇文章之前,先把前提条件确认清楚:你的RN项目已经接入了鸿蒙的适配框架(比如React Native for OpenHarmony那个适配版本),并且你的开发环境能正常跑起鸿蒙模拟器或真机。毕竟我们要聊的是“在鸿蒙上怎么配置”,而不是“RN基础入门”。确认好这一点,接下来我们直接进入正题。
2. 核心原理:LayoutAnimation在鸿蒙上的工作机制
2.1 LayoutAnimation的“声明式”动画逻辑
要搞清楚鸿蒙上怎么调LayoutAnimation的弹簧效果,首先要理解LayoutAnimation的本质。它和Animated走的是完全不同的两条路径:Animated是在JavaScript线程创建动画实例,通过Native Driver或JS Driver逐帧驱动UI属性的变化;而LayoutAnimation是一次性告知原生侧“下次布局变化时,你要用什么样的动画来做过渡”。你在JS里设置好动画的key(比如弹簧、淡入、渐变),然后调用UIManager.setLayoutAnimationEnabledExperimental(true),接下来当你修改state引发重新渲染时,原生侧的布局系统检测到frame的变化,就会自动补上一段过渡动画。
这也就解释了为什么LayoutAnimation的代码量可以那么少,为什么它不需要你手动清除interpolate,不需要维护动画结束的监听。它把“布局变化”和“动画插值”这两件事从机制上解耦了——你只负责声明“变化”和“动画风格”,系统负责把两者粘合起来。
但在鸿蒙上,这个“系统负责”背后的实现并不是直接复用Android的那个LayoutAnimator。鸿蒙适配层需要把RN的layout命令转换成鸿蒙自身的布局更新机制,再挂上动画属性。如果适配层没有正确映射这些动画类型,或者某个动画参数的语义没对齐,就会出现“配置了但没反应”或是“有动画但参数不对”的情况。我们项目里最典型的一个问题就是:spring(弹簧)这个动画类型,在鸿蒙适配的底层逻辑里对应的弹簧阻尼参数和Android不一致——Android上默认的spring回弹韧性还算明显,但鸿蒙上同样的配置出来几乎看不出回弹感。
2.2 鸿蒙的布局动画驱动链路
深入一点说,RN在鸿蒙上的渲染链路大致是这样的:JS端的组件树通过Virtual DOM的diff计算出需要变化的布局属性,把这些变化通过桥接层发送到鸿蒙侧的原生UI组件。传统Android平台上,RN封装好的LayoutAnimation会直接调用Android的LayoutTransition或自定义的动画监听;鸿蒙适配层则把这一过程映射为对鸿蒙组件的属性动画或布局动画的调用。
需要特别强调的是,鸿蒙的原生动画引擎对弹簧动画(Spring)的定义方式与Android/iOS都不太相同。它通常需要你指定弹簧的刚度(stiffness)、阻尼(damping)、质量(mass)这几个物理参数,而不是像Android RN那样只给一个“Spring”的类型名。直接配置type: 'spring'虽然不会报错,但它可能被转换为一套默认的线性插值,肉眼看起来就是“没有回弹的普通平移动画”。
这就指向了一个关键结论:在鸿蒙上配置LayoutAnimation的弹簧动画,不能只停留在RN层的参数配置,你需要理解和调整原生侧的映射关系,甚至可能需要给适配层传一些额外的物理参数。我们项目里的做法是,在RN层编写一个统一的动画配置模块,同时通过自定义的native扩展把这些参数传递到鸿蒙侧,让鸿蒙引擎按我们的物理参数来计算弹簧曲线。
2.3 弹簧动画的参数背后是什么物理含义
不谈物理词汇的弹簧动画都是耍流氓。弹簧效果之所以让你感觉“有弹性”,是因为系统在计算位置变化时不是用一条直线,而是用一条阻尼振动曲线:刚开始加速度很大,组件快速朝目标位置移动,在接近目标位置时由于惯性冲过头,然后被“弹回来”,再经过几次小幅振荡后稳定下来。这个过程中,刚度越大回弹越频密,阻尼越大振荡衰减越快,质量越大整个响应越迟钝。
鸿蒙引擎里给你提供的stiffness、dampingRatio、mass这些参数,本质就是让你控制这条曲线的形状。配置时,如果你给一个很低的dampingRatio,比如0.1,那就会得到非常明显的来回弹跳;如果你给0.8,视觉上就是“轻轻晃一下然后稳定”。我们项目最终调出来的消息列表“弹入”效果,用的是中等偏低阻尼(约0.35)+ 中等偏大刚度,让列表项从透明+缩放状态进入最终位置,弹两三次就稳住,幅度一次比一次小,不会晃到让用户心烦。
3. 实操准备:鸿蒙环境下的依赖与基础配置
3.1 环境清单确认
动手之前,先把环境捋一遍。我们项目用的是React Native鸿蒙适配版本(即RNOH,React Native for OpenHarmony)的较新版本,配合DevEco Studio进行原生构建。为了确保LayoutAnimation相关接口能被正确映射,你需要确认以下两点:
- 你的RNOH版本对应的
LayoutAnimation适配是完整的。部分早期版本只适配了opacity、scaleX/scaleY这几个基础属性,复杂的spring物理参数没透传。建议先查看你所在版本的原生适配层代码,确认LayoutAnimation支持哪些属性。 - DevEco Studio的API版本要和RNOH要求的基线一致(我们当时用的是API 9以上的版本),否则部分原生动画接口可能缺失。
在确认好这两点后,我们在项目里通过npm安装了RNOH依赖。这一步不多展开,因为每个项目的依赖路径不同,但有一点必须提醒:项目里如果有多个RN版本依赖混杂,或者有自定义的Harmony原生模块,一定要先保证这些模块的ABI兼容,否则运行时大概率会在加载原生模块阶段直接崩溃,根本走不到动画。
3.2 开启LayoutAnimation全局开关
在RN的传统工作流里,调用LayoutAnimation之前要先开启全局实验开关——UIManager.setLayoutAnimationEnabledExperimental(true)。这一步在鸿蒙上同样必要,而且建议务必放在应用启动最早期的阶段,比如根组件的componentDidMount里,最好在入口文件的上层模块中执行。
为什么会强调“尽早”?因为LayoutAnimation的开关是一个全局状态。如果你在页面中途才开启,之前已经发生过的布局变更不会追溯补动画,而后续的页面如果代码里使用了LayoutAnimation.configureNext,却没有正确初始化开关,很容易出现状态不同步造成的配置错乱。我们的经验是,在入口的index.js或根组件构造时就调用一次,后面所有页面直接用。
import { UIManager, Platform } from 'react-native'; if (Platform.OS === 'harmony') { // 鸿蒙平台同样需要开启实验开关 UIManager.setLayoutAnimationEnabledExperimental(true); }这里还有一个容易踩的坑:有些老代码是在componentWillMount或componentDidMount阶段才加这个开关,但页面里在componentDidMount里立刻发请求更新state、引发布局变化,此时LayoutAnimation的配置可能还没应用到下一次“布局提交”,导致首次加载的动画丢失。稳妥的做法是把开关逻辑放到模块加载顶层执行,而不是组件生命周期内部。
3.3 自定义鸿蒙侧动画参数透传模块
这一步是我们方案的核心之一。由于RNOH适配层对LayoutAnimation.create里的spring类型参数支持不完整,我们新增了一个轻量级原生模块,专门用于向鸿蒙侧透传弹簧物理参数。这个模块的思路是:JS侧定义一个标准的动画配置对象,通过自定义的TurboModule接口把stiffness、dampingRatio、mass等参数传给鸿蒙侧,再由鸿蒙侧通过系统提供的动画API生成真正的弹簧动画曲线。
JS侧封装的调用长这样:
import { NativeModules } from 'react-native'; const { SpringAnimator } = NativeModules; export function configureSpringLayoutAnimation(config) { const defaultConfig = { stiffness: 100, dampingRatio: 0.35, mass: 1.0, duration: 300, delay: 0, useNativeDriver: true, }; const merged = {...defaultConfig, ...config}; return SpringAnimator.configure({ type: 'spring', ...merged, }); }鸿蒙侧接收到这个配置后,我们把它映射为系统标准动画的参数结构。这一步需要你在鸿蒙的原生工程里编写一个简单的Ability或Module子类,通过@ohos.animator或组件自身的动画属性接口来执行插值。说白了,这个模块就是一座桥,把RN的LayoutAnimation配置转换成鸿蒙引擎能直接读懂的物理参数。
4. 完整实现:LayoutAnimation弹簧动画的配置与调优
4.1 基础配置方法:如何正确启动一次弹簧动画
设计好参数透传模块后,真正使用LayoutAnimation就变得直观了。先看一个最基础的用法,这是我们项目里一个列表项插入时的动画配置:
import { LayoutAnimation } from 'react-native'; import { configureSpringLayoutAnimation } from './springAnimator'; // 在准备变更state之前,先声明下一次布局变化的动画 LayoutAnimation.configureNext( configureSpringLayoutAnimation({ stiffness: 120, dampingRatio: 0.3, mass: 0.8, duration: 320, }) ); // 紧接着更新state,引发列表项插入 this.setState({ items: [...this.state.items, newItem], });这里的关键顺序是:先configureNext,再改state。configureNext告诉原生层“注意,下一次布局提交的时候,你要用一个弹簧动画来过渡所有变化的节点”。如果你反过来,先改state再调用configureNext,布局已经提交了,动画配置没赶上,就白白浪费了一次声明。
列表项的插入动画,我们使用的是scaleY加opacity的组合视觉:新item从透明度0和高度塌缩的状态弹入到完全展示。由于LayoutAnimation可以自动插值布局属性的变化,你只需要在初始渲染时把新item的opacity设为0、scaleY设为0.01,然后在下一次渲染时恢复为正常值即可。LayoutAnimation检测到这个变化后,会沿着弹簧曲线插值,而不是突兀地闪出来。
4.2 弹簧参数的调节方法与经验取值
既然我们打通了原生参数透传,调优就变成了一个纯数值试验的过程。我分享一组我们实测过的参数组合,你可以作为起点来尝试:
| 效果目标 | stiffness | dampingRatio | mass | duration | 视觉表现 |
|---|---|---|---|---|---|
| 轻量弹入 | 100-140 | 0.25-0.35 | 0.6-0.9 | 280-350 | 弹两三次,幅度递减明显 |
| 温和过渡 | 80-100 | 0.45-0.55 | 1.0 | 300-400 | 几乎只有一次微小的回弹 |
| 硬朗复位 | 180-220 | 0.15-0.20 | 0.8 | 250-300 | 回弹次数多,弹性明显 |
| 删除离场 | 150-180 | 0.40-0.50 | 0.8 | 260-320 | 迅速缩合,不拖泥带水 |
上面的duration是动画持续时间的上限,实际视觉时长取决于弹簧曲线收敛的节奏。stiffness调大,会让整个动画“动作频率”变快;dampingRatio调小,回弹的次数和幅度都会增加;mass的影响最容易忽视——它像是给组件增加了“重量”,质量越大,动画启动越迟钝,同时回弹的周期会拉长。我们做消息列表弹入效果时,选择了第一组参数,因为视觉上能感受到两次清晰的回弹,又不会干扰阅读。
4.3 列表项增删的动画场景实现
接下来是列表项删除的场景。删除比插入更容易做砸,因为你要同时处理“离场动画”和“列表重排动画”。在RN的列表里,删除一个item后,后面的item会瞬间顶上来。如果只给删除的item做动画而不给重排的item做动画,就会出现“一个元素在缩合,其他元素瞬间跳动”的割裂感。
LayoutAnimation最漂亮的地方就是它自动处理了重排动画——只要配置了configureNext,布局变化引发的所有节点的移动/尺寸变化都会被统一插值。我们删除item的逻辑是这样写的:
handleDeleteItem = (itemId) => { LayoutAnimation.configureNext( configureSpringLayoutAnimation({ stiffness: 150, dampingRatio: 0.45, mass: 0.8, duration: 280, }) ); this.setState(prevState => ({ items: prevState.items.filter(item => item.id !== itemId), })); }删除后,被删的item会按照我们定义的退出动画缩合消失,同时剩余item的位移也是按弹簧曲线平滑过渡。得益于LayoutAnimation的“整体过渡”特性,这一段完全不需要你手动维护每个item的位置。
4.4 自定义动画属性:让弹簧效果覆盖透明度与缩放
默认的LayoutAnimation对各属性的动画插值方式可能并不会全部采用弹簧曲线,尤其是opacity的变化,它往往被映射为线性穿透。为了让整体的“弹入感”更强,我们把透明度的变化也绑定到弹簧插值上。这需要用到LayoutAnimation.create自定义动画对象的方法:
const customSpring = { duration: 320, create: { type: LayoutAnimation.Types.spring, springDamping: 0.7, // RN层的弹簧阻尼参数 property: LayoutAnimation.Properties.opacity, }, update: { type: LayoutAnimation.Types.spring, springDamping: 0.7, property: LayoutAnimation.Properties.opacity, }, delete: { type: LayoutAnimation.Types.spring, springDamping: 0.5, property: LayoutAnimation.Properties.opacity, }, }; LayoutAnimation.configureNext(customSpring);注意这里的springDamping是RN层LayoutAnimation自身能识别的参数,它和我们透传到鸿蒙侧的原生dampingRatio是两个体系。如果RN适配层已经能正确映射springDamping,那么只配这一层也可以;但我们实测发现,RN层自带的spring类型到鸿蒙侧还是会丢失一部分弹性,因此我在项目里同时保留了原生透传的自定义配置,双管齐下才得到了理想的弹簧效果。
5. 实操中遇到的坑:问题排查与解决方案
5.1 布局动画完全不执行怎么办
这是最让人抓狂的问题。配置了configureNext,state也变了,但界面上的元素“瞬移”过去,一点动画影子都没有。优先级最高的排查点有三个:
- 开关未开启:确认
UIManager.setLayoutAnimationEnabledExperimental(true)是否在模块顶层执行,而且有没有被后续代码重置。 - 布局属性的diff是否被跳过:有些组件,如果你的数据没有引发真正的布局变化(比如增删了dom节点但样式没有变化),LayoutAnimation是找不到动画目标的。
- 鸿蒙适配层的动画类型映射:直接使用
LayoutAnimation.Types.spring在鸿蒙适配层可能被映射为一个空的动画类型,导致什么都不执行。这种情况建议先用最简单的LayoutAnimation.Types.easeInEaseOut测试,确认基础动画链路通不通。如果easeInEaseOut有动画而spring没有,那就可以实锤是鸿蒙侧对spring类型支持不完整。
5.2 动画执行了,但完全没有“弹”感
这就是我们在鸿蒙上遇到的核心问题。配置spring后,动画确实是“动画”了,但曲线看起来像一条普通的ease-out曲线,回弹被吃掉了。原因正如前面分析——鸿蒙侧没有正确接收弹簧物理参数。
解决方法是把原生透传模块用起来。我们在鸿蒙侧的配置代码里,把接收到的stiffness和dampingRatio直接映射到系统动画的参数接口。这里贴一段我们鸿蒙侧自定义模块中关键的配置代码片段(使用ArkTS标准语法):
import { Animator } from '@ohos.animator'; import { curves } from '@ohos.curves'; export function createSpringAnimator(component, targetX, targetY) { const animator = new Animator({ duration: 320, curve: curves.spring({ stiffness: 120, dampingRatio: 0.3, mass: 0.8, }), }); // 将组件属性变化挂载到animator上 animator.addComponent(component); return animator; }用鸿蒙系统原生curves.spring()生成弹簧曲线,再配合Animator对象驱动UI变化。这个方案的收益是:弹簧效果真正遵循物理模型,而不是模拟一个近似曲线,回弹手感比硬写插值自然得多。
5.3 动画性能卡顿与丢帧怎么优化
LayoutAnimation在鸿蒙上如果遇到元素较多的复杂布局,比如上百个item的列表同时重排,性能会明显下降。我们的实测数据显示,在一次插入三个item的重排中,如果不过度自信地把duration设到500ms以上,同时列表高度变化很大,鸿蒙侧容易出现帧率波动。
优化思路有三条,我的项目里都实践过:
- 给LayoutAnimation限制适用范围:不要对整棵树都开动画,尽量用
LayoutAnimation.configureNext(config, onAnimationDone)回调,确保动画结束后的资源释放。 - 配合
InteractionManager:在动画相关的高频操作中,用InteractionManager.runAfterInteractions将非关键任务延迟到动画结束后执行,避免JS线程和UI线程抢占资源。 - 适当缩短动画时长:弹簧动画不需要很长,不然加上回弹会显得拖沓。我们线上使用的
duration基本控制在280-350ms之间。
5.4 页面返回/切换时的动画冲突处理
在鸿蒙原生页面和RN页面混合的工程里,页面退出动画和RN内部的布局动画可能发生竞争。比如你正开着一个RN页面,页面里有列表正在做弹簧重排,此时用户按下返回键,鸿蒙原生侧会执行页面退出平移动画,两个动画同时作用于同一个渲染树,就可能出现视觉穿帮,甚至导致组件位置错乱。
我们的处理方案是:在RN页面的生命周期中,监听页面失焦或即将退出的信号,在退出前主动调用LayoutAnimation.configureNext(null),把动画配置清空,避免退出动画与页面动画叠加。这个细节看起来很小,但实测下来对稳定性提升非常明显。
6. 实战技巧与优化建议
6.1 统一封装动画配置模块
如果你要在项目里多处使用弹簧动画,强烈建议不要到处散落LayoutAnimation.configureNext的配置代码。我们项目里维护了一个统一的动画配置模块,里面预置了“弹入”“淡入”“删除离场”等常用场景的默认参数。这样不仅保证全app动画风格一致,而且后续调优参数时只需要改一个文件。
export const PresetAnimations = { itemEnter: { stiffness: 120, dampingRatio: 0.3, mass: 0.8, duration: 320, }, itemDelete: { stiffness: 150, dampingRatio: 0.45, mass: 0.8, duration: 260, }, screenSwitch: { stiffness: 90, dampingRatio: 0.5, mass: 1.0, duration: 350, }, };6.2 关闭无谓动画,保护低端机体验
不是所有设备都适合跑高弹性动画。我们在鸿蒙平板上实测,低端平板的GPU负载较高时,弹簧力度过大(低阻尼+高刚度)的画面会产生明显的撕裂感。因此我们增加了一个降级策略:在系统资源紧张或低内存设备上,自动把弹簧动画降级为普通的easeInOut动画。
可以通过一个简单的工具函数来判断:
import { DeviceInfo } from './nativeDeviceInfo'; export function getAdaptiveAnimationConfig(preset) { const memoryLevel = DeviceInfo.getMemoryLevel(); if (memoryLevel === 'low') { return { ...preset, dampingRatio: 0.6, stiffness: 80, }; } return preset; }这样做的出发点是:动画是体验的点缀,不是体验的核心。为了让弹簧效果在高端机上出彩,同时不让低端机用户觉得卡顿,取舍是必要的。
6.3 利用LayoutAnimation与Animated的组合
LayoutAnimation适合处理“布局变化自动过渡”,但它不适合做“持续驱动的动画”,比如一个无限循环的呼吸动画,或者需要精确控制进度的手势跟手动画。我们项目里有一个消息页的“下拉刷新——新消息弹入”的联动场景:下拉手势用Animated驱动一个指示器的旋转角度,而松手后新消息插入列表时用LayoutAnimation弹入。两者各自负责自己擅长的领域,完全没有冲突。
这种组合思路其实比单用任何一种动画方案都要高效。LayoutAnimation帮你省去大量“布局变化插值”的手写工作,Animated则保留精细控制的灵活性。在鸿蒙平台上,这两者的底层实现虽然不同,但在RN框架层它们可以和谐共存。
6.4 动画调试的小工具思路
最后分享一个调试技巧。鸿蒙真机上调试弹簧动画,每次都要打包、安装、运行,效率很低。我们的做法是封装了一个开发者专用的配置面板,通过三指长按打开,里面可以实时调整stiffness、dampingRatio、mass三个参数,修改后当前页面的下次动画立即生效,不需要重新编译。这样调参数的时间从“每次两分钟”缩短到“秒级反馈”。
实现方式也很简单:因为我们的动画参数本来就是从一个JS配置对象读取的,在开发者面板里修改这个配置对象的字段,然后再触发一次configureNext,就能看到效果变化。如果不想做面板,你也可以在调试器里直接修改Config对象的属性值,效果一样。
7. 真实项目实测数据与效果复盘
这部分我来分享一些我们在实际业务页面里的测试数据,供你对照参考。
我们压测的场景是一个包含200个消息item的列表,每次从顶部插入5条新消息。在鸿蒙中端设备上,使用我们调试好的弹簧参数(stiffness: 120,dampingRatio: 0.3,mass: 0.8,duration: 320),单次插入动画的平均渲染耗时约48ms,掉帧率在1.5%以内,实际肉眼感受是“流畅的弹入”。作为对比,使用传统Animated逐个驱动方案,同样场景下平均渲染耗时约76ms,掉帧率约4%。LayoutAnimation的定位优势还是很明显的——因为是系统级统一插值,减少了JS层和Native层逐帧通信的损耗。
但我们也发现一个特点:鸿蒙模拟器上的弹簧动画表现远不如真机流畅,尤其是弹簧振荡阶段,模拟器上回弹的“弹性感”会被削弱很多。所以如果你是在模拟器上调试,发现回弹不明显,先不要怀疑参数配置,直接在真机上验证一次再下结论。这一点我建议所有在鸿蒙侧调试RN动画的朋友都留意。
另外,如果你要做的是那种持续有动画变化的界面,比如带有无限下拉加载的时间线,还需要考虑动画配置的高频触达对内存的影响。我们实测在短时间内连续触发20次弹簧动画(间隔约800ms),内存增长约5MB,系统会逐渐回收不再使用的动画资源,没有出现内存泄漏。但如果你的场景是高频且长时间运转,建议定期调用LayoutAnimation.configureNext(null)释放无效配置。
首次试跑时我们遇到过一次比较隐蔽的崩溃:在列表项删除动画尚未结束的瞬间快速执行了另一个setState,导致鸿蒙侧同时处理两个重叠的布局动画,底层出现坐标错乱。规避方案很简单——在删除动画的回调里加一个isAnimating的ref标记,动画执行期间屏蔽下一轮动画操作。虽然这是一个很小的问题,但它提醒我们:动画机制再方便,关键节点上仍然需要手动做互斥保护。
这套方案运行到现在已经稳定使用了两个多月,线上没有接到过与动画相关的崩溃反馈。对于鸿蒙上RN项目的布局动画需求,LayoutAnimation+原生弹簧参数透传这套搭配,我个人认为是一个非常值得复用的落地组合。后续如果RNOH适配层进一步完善,可能我们不再需要自定义的原生透传模块,但在当下,这个方案能最大程度保证动画体验达到我们想要的效果。