React Native for OpenHarmony 实战:三方库 react-native-animated-spinkit 的鸿蒙化适配指南
本文记录把 react-native-animated-spinkit(12 种 CSS 风格加载动画)适配到 HarmonyOS 的完整过程。
这个库的特殊之处在于:它没有任何返回值可以断言。它是纯视觉效果——12 个会转、会跳、会缩放的加载指示器。所以本轮的核心问题是:"动画在跑"这件事,怎么验?
答案是把屏幕当被测对象:连拍两帧,逐像素比对。


一、先说结论
| 项 | 结果 |
|---|---|
| 上游最新版 | 1.5.2(npm gitHead = aa452f78…) |
| 是否需要原生适配 | 不需要——纯 JS,12 个组件全靠 Animated 驱动,无原生模块、无 dependencies |
| 与 npm 上游的差异 | 98 / 99 个文件去掉 CR 后逐字符一致,唯一差异是 package.json |
| 自动链接信号 | linked 10 libraries, skipped 1 libraries,计数未增加 |
| 编译 | assembleHap 6 分 55 秒;HAP 81,516,680 → 81,614,985 字节(+98,305 ≈ 96 KB) |
| 设备侧断言 | ✅ 35 / 35 全部通过 |
| 原生动画驱动 | ✅ useNativeDriver: true 真的生效(RNOH 提供 NativeAnimatedTurboModule,无回退告警) |
| 帧率 | requestAnimationFrame 实测 63.7 fps |
| 像素级证据 | 动画中两帧 6.17% 像素显著变化(复测 6.16%);停下后 0.00%(一个像素都不变);隐藏后对照区整片纯白 |
| 交付包缺陷 | ⚠️ 4 处,其中 npm install 与 npm pack 退出码都是 1 |
| 一个重要事实 | 这个库没有 default 导出——只有 12 个具名导出 |
一句话结论:功能完全可用,12 个动画在鸿蒙上都能正常渲染并驱动(走的是真正的原生动画驱动,满帧率)。但交付包在工程配置与文档上有 4 处问题,其中 npm install 失败会直接影响 CI。
二、判定过程:这个库不需要原生适配
判断一个 RN 库要不要做原生适配,我固定走三步:
| 步 | 做法 | 这个库的结果 |
|---|---|---|
| ① | npm view <包名> harmony --json 看有没有 harmony.autolinking | 没有 |
| ② | 仓库里有没有 harmony/(以及 android/、ios/) | 都没有 |
| ③ | 代码里有没有 NativeModules / Platform.OS / requireNativeComponent | 有 Platform.OS,但只用来决定 useNativeDriver 的取值 |
第 ③ 步命中的那处 Platform.OS 值得看一下,因为它恰好是这个库在鸿蒙上最大的运行时风险点:
// src/utils.ts
const timing = Animated.timing(value, {
duration,
easing: createKeyframeEasingFunction(keyframes, easing),
toValue,
useNativeDriver: Platform.OS !== 'web', // ← 鸿蒙上是 true
})
stagger() 里更直接,非 web 分支写死了 useNativeDriver: true。
为什么这是风险点:useNativeDriver: true 要求平台提供原生动画模块。如果平台没有,React Native 会打印回退告警并改用 JS 驱动——动画照跑,但驱动方式、性能和语义都变了。所以"它能不能跑"这个问题,必须换成"它的原生驱动到底有没有生效"。
这一点留到第五节和第七节解决。
公开 API(12 个组件,全部具名导出):
Plane Bounce Pulse Swing Chase Wave
Flow Circle CircleFade Grid Fold Wander
⚠️ 这个库没有 default 导出。 三处入口文件(src/index.ts、lib/commonjs/index.js、lib/module/index.js)都只有具名导出,我在设备上专门断言了 typeof default === 'undefined'——通过。
组件属性(SpinnerProps):
| 属性 | 默认值 | 说明 |
|---|---|---|
size | 48 | 单位是 vp |
color | '#000' | 填充色 |
animating | true | 是否播放 |
hidesWhenStopped | true | animating=false 时是否整体隐藏(opacity 0) |
三、这个交付包长什么样:与 npm 上游逐字节一致
核对方法:与 react-native-animated-spinkit@1.5.2 的 npm tarball 逐文件比对(先去 CR 再逐字符比较)。
结果:98 / 99 个文件完全一致,包含 src/ 下 15 个源文件与 lib/ 下全部构建产物。唯一不同的是 package.json:
| 字段 | 变化 |
|---|---|
description | 追加 " for React Native for OpenHarmony" |
scripts | 新增 test,其余(含 prepare: bob build)原样保留 |
repository.url | 改为 AtomGit 地址 |
homepage | 改为 AtomGit 地址 |
files | 删除了上游的白名单(上游为 ["src","lib","!**/__tests__",...]) |
peerDependencies | 收窄为 react: >=19 / react-native: >=0.84 <1(上游是 *) |
实现文件零改动,所以后面发现的问题都属于工程配置与文档层面,与动画实现无关。
四、接入宿主与构建运行
纯 JS 库的接法只有三处:
// package.json
"react-native-animated-spinkit": "file:../react-native-animated-spinkit"
// metro.config.js —— file: 装进来是 junction,必须让 Metro 找得到源码
watchFolders: [path.resolve(__dirname, '../react-native-animated-spinkit')],
// index.js
AppRegistry.registerComponent('SpinnerTestApp', () => SpinnerTestApp);
不要动 harmony/entry/oh-package.json5(不需要 HAR),不要动 module.json5(不需要权限)。信号是自动链接计数不增加:
info updated 4 file(s), linked 10 libraries, skipped 1 libraries
| 项 | 数值 |
|---|---|
首次 assembleHap | 6 分 55 秒 |
| 二次重编(改了两条断言) | 6 分 28 秒 |
| HAP 变化 | 81,516,680 → 81,614,985 字节(+98,305 ≈ 96 KB) |
| bundle 产物 | bundle.harmony.js 6,454,621 字节 |
⚠️ 这里有个陷阱:
npm install实际上是失败的(退出码 1),但因为编译不检查 npm 退出码,本地"一切正常"。详见第八节。
五、验证设计:纯视觉效果怎么验才可信
这类库没有返回值、没有系统状态可读。唯一能验的对象是屏幕上的真实产物。
5.1 手段:连拍两帧 + 逐像素比对
设备侧连拍两帧(间隔 260 ms),把图拉回 PC,用 System.Drawing 逐像素比较:
hdc shell "snapshot_display -f /data/local/tmp/tag-a.jpeg"
# 等 260 ms
hdc shell "snapshot_display -f /data/local/tmp/tag-b.jpeg"
hdc file recv /data/local/tmp/tag-a.jpeg E:\rnoh-work\frames\tag-a.jpeg
hdc file recv /data/local/tmp/tag-b.jpeg E:\rnoh-work\frames\tag-b.jpeg
判据用每通道最大差 > 24 的像素占比。选 24 是因为:两张完全相同的 JPEG 截图之间也会有几个灰阶的压缩噪声,而 24 能干净地把"噪声"和"真的变了"分开。
5.2 页面要提供一个"只有一个会动的东西"的区域
测试页里放了一个固定的 240×240 vp 白色对照区,里面只放一个 spinner;12 个组件的展示网格可以一键隐藏。这样整个页面只剩一个动画,裁剪区的像素变化就只可能来自它。
裁剪区不能写死坐标——必须从每一帧里自动定位那个白色方块。我用的办法是找"包含长连续白色游程"的行(白框约 720 物理像素宽,而页面上的白字游程远短于此),取这些行的外接矩形。
5.3 三个(其实是四个)场景构成闭环
只证明"画面在变"是不够的:变的原因可能是噪声、可能是别的东西在动。所以必须有对照组:
| 场景 | 预期 | 作用 |
|---|---|---|
animating=true | 两帧明显不同 | 动画在跑 |
animating=false + hidesWhenStopped=false | 两帧完全相同,且区域仍有非白像素 | 动画真的停了;且被测对象仍可见 |
animating=false + hidesWhenStopped=true | 两帧相同,且区域整片纯白 | 隐藏是有效的 |
恢复 animating=true | 两帧再次明显不同 | 可重复 |
第二个场景里"区域仍有非白像素"这一条不能省。 否则第三个场景的"整片纯白"有可能只是因为根本没渲染出来——那就不是"隐藏生效",而是"东西不在"。
六、实测发现一:动画真的在跑,停也真的停
设备:Pura X View 模拟器,1320×2232(density 3)。对照区实测为 720×720 物理像素(即 240 vp 见方),裁剪时向内缩 14 像素取 692×692。
| 场景 | 状态 | 显著变化(>24) | 任意差异(>0) | 非白像素 | 区域平均色 |
|---|---|---|---|---|---|
wave_on | animating=true | 6.17% | 9.22% | — | — |
wave_frozen | animating=false, hides=false | 0.00% | 0.00% | 14.66% | (224,235,252) |
wave_hidden | animating=false, hides=true | 0.00% | 0.00% | 0.00% | (255,255,255) |
wave_on2 | 恢复 animating=true | 6.16% | 9.21% | — | — |
四条结论,逐条都有关键意义:
- 动画在跑:260 ms 间隔的 6.17% 像素变化,而且复测得到 6.16%——可重复;
- 停下是真的停:
animating=false后,两帧之间任意差异 0.00%——连一个像素都没变。这比"变化变小了"强得多,是"动画被真正停止"的直接证据; hidesWhenStopped生效:整片纯白,平均色精确为 (255,255,255);- 静止时 spinner 确实还画在屏幕上(14.66% 非白像素)⇒ 证明第 3 条的"全白"是真的被隐藏,而不是对照区空了。
6.1 换 spinner 复验
| spinner | 结构 | 显著变化(>24) |
|---|---|---|
| Wave | 5 条竖条 + scaleY | 6.17% / 6.16% |
| Circle | 12 个圆点环形分布 + translate | 3.64% |
| Grid | 9 宫格 + 缩放/淡入淡出 | 23.26% |
三种完全不同的结构都在动,说明不是某个组件的偶然行为。
6.2 一个测出来的上游设计细节
12 个组件的根视图尺寸一起量了一遍(onLayout):
Plane=48x48 Bounce=48x48 Pulse=48x48 Swing=48x48
Chase=48x48 Wave=48x48 Flow=48x12 Circle=48x48
CircleFade=48x48 Grid=48x48 Fold=48x48 Wander=48x48
11 个是 size × size 的正方形,只有 Flow 是 size × size*0.25。 这是上游的设计(Flow 是 3 个横排圆点,容器只需 1/4 高度),不是缺陷——但如果有人写"所有 spinner 都是正方形"的通用布局代码,会在 Flow 上踩到。
(我第一版断言就写死了 size × size,于是在 Flow 上失败了一次——这是我自己按想象写断言,不是库的问题。)
七、实测发现二:useNativeDriver: true 在鸿蒙上真的生效
这是第二节留下的风险点。判断方法不能靠猜,要靠权威证据。
7.1 权威证据一:原生动画 TurboModule 存在
#RNOH_CPP: TurboModuleFactory.cpp:54> Creating Turbo Module: NativeAnimatedTurboModule
#RNOH_ARK: TM created: NativeAnimatedTurboModule
RNOH 提供了 NativeAnimatedTurboModule。页面内用 TurboModuleRegistry.get('NativeAnimatedTurboModule') 也能拿到非空对象。
7.2 权威证据二:日志里没有回退告警
如果原生动画模块缺失,React Native 会打印 useNativeDriver is not supported because the native animated module is missing 之类的告警并回退。整段运行日志里没有这类告警。
⇒ useNativeDriver: true 真的生效,没有回退。
7.3 ⚠️ 一个我自己用错的判据(值得记下来)
我最初在测试页里是这么判断驱动方式的:
「原生驱动下,JS 侧的
Animated.Value.addListener不会被调用;如果 listener 有更新,说明回退到了 JS 驱动。」
实测把这个判据推翻了:JS 驱动时 listener 更新 1 次,原生驱动时 listener 更新 18 次。按我的判据,会得出"回退到 JS 驱动"这个完全相反的错误结论。
真实原因是:React Native 的原生驱动本身会通过 onAnimatedValueUpdate 事件把动画值回传给 JS 监听,所以两种驱动下 listener 都可能被调用。
教训:结论要建立在权威证据(模块是否存在、日志有没有告警)上,不能建立在自己推演出来的启发式上。启发式看起来很像证据,但它只是猜测。
7.4 帧率
页面内用 requestAnimationFrame 数帧:
requestAnimationFrame 计帧 = 64 帧 / 1005 ms ≈ 63.7 fps
满帧率。配合"原生驱动生效"的结论,说明动画在鸿蒙上是由原生侧逐帧驱动的,不是靠 JS 定时器凑出来的。

八、实测发现三:交付包的 4 处问题
8.1 npm install 与 npm pack 的退出码都是 1(最严重)
npm error code 1
npm error command C:\windows\system32\cmd.exe /d /s /c bob build
npm error 'bob' is not recognized as an internal or external command
根因:交付包的 scripts 与上游只差一个 test——也就是原样保留了 "prepare": "bob build"。而 react-native-builder-bob 是 devDependencies,在消费者侧和纯净检出里都不可得。
prepare 这个生命周期钩子在本地路径依赖(file:)与 git 依赖安装时都会执行,所以:
| 操作 | 实测结果 |
|---|---|
宿主 npm install(file: 依赖) | exit 1 |
npm pack | exit 1,无产物 |
为什么本地没炸:npm 在跑 prepare 之前就已经建好了 junction,所以 node_modules/react-native-animated-spinkit 存在、Metro 能解析、HAP 照样编译成功。“能编译过"掩盖了"安装已经失败”。
实际风险有两条:
- 任何以
npm install退出码作为门禁的 CI/CD 会在这一步中断——这是最现实的影响; npm pack打不出 tgz ⇒ README 里推荐的npm install /path/to/react-native-animated-spinkit-1.5.2.tgz这条路径无法从该仓库自行产出。
修复方向:交付包把 prepare 删掉或换成无害脚本即可(bob 只在需要重新构建 lib/ 时才有意义,而见下一条,RN 侧根本不加载 lib/)。
说明:README 里还有一条 git 安装写法
npm install https://atomgit.com/...#1.5.2-ohos-1.0.0。我实测这条路径时先败在归档下载(npm error code TAR_BAD_ARCHIVE),属环境问题,不作为证据。按 npm 的规则,git 依赖同样会执行prepare。
8.2 README 说 Metro 用 lib/commonjs 入口,实测是 src
README 原文:「Metro 使用 lib/commonjs/index.js 入口。」
在构建产物里数引用:
react-native-animated-spinkit/src 出现 16 次
react-native-animated-spinkit/lib/commonjs 出现 0 次
根因:package.json 里 main 指向 lib/commonjs/index.js,但还有一个 react-native 字段指向 src/index.ts。React Native 的 Metro 默认 resolverMainFields = ['react-native', 'browser', 'main']——react-native 字段优先,所以加载的是 TypeScript 源码,由 Metro 现场转译。
影响:lib/(commonjs + module + typescript 三份产物,约 150 KB)对 RN 消费者而言是死代码;而 README 的入口说明与实际正好相反,会误导做入口排查的人。
8.3 README 的用法示例用了 default import,而库没有 default 导出
README 给的示例是:
import Library from 'react-native-animated-spinkit';
const Library = require('react-native-animated-spinkit');
实测:三个入口文件都没有 default 导出;设备侧断言 typeof default === 'undefined' 通过。
⇒ 照 README 抄会拿到 undefined。正确写法:
import {Plane, Wave, Circle} from 'react-native-animated-spinkit';
8.4 契约测试不测库
__tests__/harmony-contract.test.cjs 全文只断言四件事:
assert.equal(pkg.name, 'react-native-animated-spinkit');
assert.equal(pkg.version, '1.5.2');
assert.ok(require.resolve(path.resolve(__dirname, '..', pkg.main)));
assert.ok(fs.existsSync(path.resolve(__dirname, '..', 'README.OpenHarmony_CN.md')));
没有渲染任何组件、没有触发任何动画。 它验证的是"包文件存在",不是"库能用"。
这个测试没有虚假命名——README 就称它为"包级 smoke test",所以这是覆盖面不足,不是欺骗。但注意:require.resolve 的目标是 pkg.main,而实际被 Metro 加载的是 src/index.ts——也就是说这个测试连"真正的入口文件是否存在"都没覆盖到。
8.5 spec.json 的可追溯性偏弱
"upstream": {"type": "git", "url": "git+https://github.com/tienphaw/react-native-animated-spinkit.git"},
"native": false,
"validation": "pass"
upstream是对象(同类交付包一般用字符串),形态不一致;- 没有
upstreamCommit——上游gitHead(aa452f78…)我从 npm 查到了,但规格文件里没记; validation是裸字符串(同类交付包一般是含场景数、设备、HAP 哈希的对象)。
三个字段都偏弱,导致可追溯性只能靠人去读 README,无法脚本化核对。
8.6 另外两处小问题
- README 的「能力一览」只有 2 行、表述空泛(“组件或工具 API 支持”),没有列出 12 个 spinner 的名字——而这个库的全部价值恰恰就是"有哪 12 个动画";
- 上游
package.json的files白名单被删掉了,发布内容不再受限。
九、已知限制
9.1 库本身的
- 没有 default 导出,必须用具名导入。照错误文档写会拿到
undefined。 Flow的根视图是size × size*0.25,其余 11 个是size × size。写通用布局代码时要留意。animating=false时动画被reset()、值归 0,也就是相位会丢失——重新设回true是从起点重新开始,不是从暂停处继续。hidesWhenStopped只改 opacity,不影响布局;所以"隐藏"后元素仍占位。- 所有 spinner 都依赖
useNativeDriver: true(非 web)。如果某个平台的 RN 实现不支持原生动画驱动,会静默回退到 JS 驱动——动画仍可跑,但驱动方式与性能会变。在鸿蒙上这一点已确认没问题(第七节)。
9.2 本次验证的边界
- 只在一台模拟器上验证(
Pura X View,1320×2232,density 3),没有真机。 - 12 个 spinner 只逐像素验证了 3 个(Wave、Circle、Grid)。其余 9 个只验证了"渲染成功 + 根尺寸正确",没有逐个确认动画观感与原版一致。
- 没有校验动画周期:源码里各 spinner 的
duration从 1200 ms 到 2500 ms 不等,我只确认"在动",没有测周期是否精确。 - 没有断言相位:
animating中途切换的行为(是否从 0 重新开始)在协议里发生过,但没有做相位断言。 - 没有跨平台视觉对照:没在 Android/iOS 上跑同一份用例比对观感。
- 帧率只测了空闲状态(63.7 fps);高负载/低端设备未测。
- 组合场景未测:12 个动画同时播放时的帧率、以及页面滚动时的表现。
- 交付包的 git 安装路径未验证(本次败在归档下载,属环境问题)。
未改动库代码。 发现的 4 处问题全部属于工程配置与文档层面。
十、常见问题
Q1:这个库需要 HAR、权限或 ohpm 接线吗?
都不需要。它是纯 JS,12 个组件全靠 Animated 驱动,没有原生模块。判断信号是 link-harmony 的计数不增加(本次输出 linked 10 libraries, skipped 1 libraries)。
Q2:怎么引?
用具名导入——这个库没有 default 导出。
// ✅ 正确
import {Plane, Wave, Grid} from 'react-native-animated-spinkit';
// ❌ 拿到 undefined
import Spinkit from 'react-native-animated-spinkit';
Q3:有哪些 spinner?
import {
Plane, Bounce, Pulse, Swing, Chase, Wave,
Flow, Circle, CircleFade, Grid, Fold, Wander,
} from 'react-native-animated-spinkit';
共 12 个。组件属性是 size(默认 48)、color(默认 #000)、animating(默认 true)、hidesWhenStopped(默认 true),其余 ViewProps 会透传到根视图。
Q4:为什么要在意 useNativeDriver?
因为这个库在非 web 平台强制使用原生动画驱动(useNativeDriver: true)。如果平台不支持,React Native 会静默回退到 JS 驱动——动画照样动,但驱动方式、性能和语义都变了。
在鸿蒙上可以确认没问题:
hdc shell "hilog -x | grep -i 'NativeAnimatedTurboModule'"
看到 Creating Turbo Module: NativeAnimatedTurboModule 且日志里没有 useNativeDriver is not supported 这类告警,就说明原生驱动生效了。
Q5:npm install 报了 'bob' is not recognized,但编译却过了,要紧吗?
要紧。 这是交付包保留了上游的 prepare: "bob build" 脚本导致的,bob(react-native-builder-bob)只是库的 devDependencies,消费者侧没有。
它之所以没耽误编译,是因为 npm 在跑 prepare 之前就已经建好了软链接,Metro 照样能解析。但:
npm install的退出码是 1 ⇒ 任何以它做门禁的 CI 会中断;npm pack也会失败,打不出 tgz。
临时绕过:用 tgz 安装(prepare 不会执行),或者忽略该步骤的退出码并把 prepare 从依赖的 package.json 里去掉。根修建议还是交付方删掉 prepare。
Q6:怎么自己确认动画真的在跑?
连拍两帧做像素差:
hdc shell "snapshot_display -f /data/local/tmp/a.jpeg"
# 等 ~260 ms
hdc shell "snapshot_display -f /data/local/tmp/b.jpeg"
hdc file recv /data/local/tmp/a.jpeg E:\tmp\a.jpeg
hdc file recv /data/local/tmp/b.jpeg E:\tmp\b.jpeg
然后在 PC 上逐像素比。记住要做一个对照:把 animating 设成 false 再拍两帧,这两帧应当完全相同。只有"动的时候有差异、停的时候没差异",差异才能归因到动画。
Q7:怎么判断"隐藏"是真的隐藏了?
看对照区里的非白像素占比:
hidesWhenStopped=true→ 应该 0%,平均色是纯白 (255,255,255);hidesWhenStopped=false(且已停止)→ 应该仍有一片非白像素(本次实测 14.66%)。
第二条不能省。 没有它,“0% 非白"有可能只是因为那个区域压根没渲染出来——那就不是"隐藏生效”。
Q8:animating 从 false 切回 true,会从原地继续吗?
不会。animating=false 时库调用的是 animation.reset() 并把值设为 0,所以再切回 true 是从起点重新开始。
小结
react-native-animated-spinkit 是个纯视觉库:12 个加载动画,没有原生模块、没有返回值、没有系统状态可读。
这轮真正要解决的问题是"怎么验一个只有视觉的东西"。做法和体会:
-
效果类输出只能靠真实产物 + 对照。 连拍两帧做像素差,一次能回答三件事:在不在动(差异 > 0)、停没停(差异 = 0)、该隐藏时是否真的不可见(整片纯白)。本次实测三档分别是 6.17% / 0.00% / 0.00%。
-
"静止时仍然可见"这条对照不能省。 有它,才能把"隐藏生效"和"区域根本没渲染"区分开——前者是功能正常,后者是测量失败,但两者的数字长得一模一样。
-
裁剪区要在每个场景里重新定位,不能复用固定坐标。 我早期拿到过一个假的 0.00%:看着像"动画确实停了",实际是页面滚动后裁剪区落在了深色背景上。一个漂亮的结果,先确认被测对象还在画面里。
-
结论要建立在权威证据上,不要建立在自己推演的启发式上。 我一开始用"原生驱动时 JS 侧 listener 不更新"来判断驱动方式,实测把它推翻了——RN 的原生驱动本身会通过
onAnimatedValueUpdate回调 JS 监听。真正的判据是"TurboModule 是否存在 + 日志里有没有回退告警"。启发式看起来很像证据,但它只是猜测。 -
“能编译过"可能掩盖"安装已经失败”。 这个交付包的
npm install退出码是 1,但因为软链接先建好了、编译又不看退出码,本地一切正常。把npm install的退出码当独立检查项,并顺手试一次npm pack。 -
文档里的"入口"和"用法"都要实测一遍。 这份 README 说 Metro 用
lib/commonjs入口(实测是src),用法示例用 default import(实测没有 default 导出)。两条都会让照文档接入的人踩空。
关于交付包:实现文件与上游逐字节一致,动画表现也完全正常;问题集中在工程配置与文档——npm install/npm pack 失败、入口说法错误、用法示例错误、契约测试不测库。其中第一条影响 CI,建议优先修;后三条改文档即可。
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-animated-spinkit(上游 1.5.2 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-animated-spinkit |
| 适配 TAG | 1.5.2-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS) |
| 上游仓库 | https://github.com/tienphaw/react-native-animated-spinkit(基线 commit aa452f7898719a2acfa466dea4b215b21eddbb21,MIT) |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = SpinnerTestApp) |
接入方式(纯 JS,零原生接线):
// package.json
"react-native-animated-spinkit": "file:../react-native-animated-spinkit"
// metro.config.js —— file: 装进来是 junction,必须让 Metro 找得到源码
watchFolders: [path.resolve(__dirname, '../react-native-animated-spinkit')],
// 必须具名导入 —— 这个库没有 default 导出
import {
Plane, Bounce, Pulse, Swing, Chase, Wave,
Flow, Circle, CircleFade, Grid, Fold, Wander,
} from 'react-native-animated-spinkit';
// 基本用法
<Wave size={48} color="#1f6feb" />
// 停止并隐藏(默认 hidesWhenStopped = true)
<Wave size={48} color="#1f6feb" animating={false} />
// 停止但保留占位
<Wave size={48} color="#1f6feb" animating={false} hidesWhenStopped={false} />
// 其余 ViewProps 会透传到根视图
<Circle size={64} color="#333" style={{marginTop: 12}} />
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey SpinnerTestApp
验证环境
| 项 | 版本 |
|---|---|
| React Native | 0.84.1 |
| React | 19.2.3 |
| RNOH(npm / ohpm) | @react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3 |
| Node.js | v24.14.0 |
| DevEco Studio | 26.0.0.621 |
| HarmonyOS SDK | API 26(26.0.0.32) |
| 设备 | HarmonyOS 7.0.0(26.0.0) Beta2 模拟器 Pura X View(ohos-x64),1320×2232,density 3 |
| 动画驱动 | 原生驱动(NativeAnimatedTurboModule 可用,无回退告警) |
| 帧率 | requestAnimationFrame ≈ 63.7 fps |
| 宿主 HAP 产物 | entry-default-signed.hap(81.61 MB) |
| 本次增量构建 | assembleHap 6 分 55 秒,HAP +98,305 字节 |
| 验证规模 | 设备侧 35 / 35 断言全部通过;像素比对逐个场景:动画中 6.17% / 6.16% / 3.64% / 23.26%,停止后 0.00%,隐藏后对照区非白像素 0.00% |
欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN
React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native
RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview
更多推荐


所有评论(0)