React Native for OpenHarmony 实战:三方库 react-native-progress-wheel 的鸿蒙化适配指南
本文记录把 react-native-progress-wheel(环形进度轮)适配到 HarmonyOS 的完整过程。
先说结论:这属于"最省事"的一类库 —— 纯 JS、零依赖、不依赖任何 SVG 库、没有一处老 API。装完直接用。
它最巧的地方是:用两个"半圆"拼出一段圆弧,完全不画 SVG:
// 外层:圆形裁剪
container: { width: size, height: size, borderRadius: size / 2, overflow: 'hidden' },
// 灰色底环(整圈)
background: { width: size, height: size, borderRadius: size / 2,
borderWidth: width, borderColor: backgroundColor, position: 'absolute' },
// 半高裁剪(高度只有一半!)
halfCircle: { width: size, height: size / 2, overflow: 'hidden', position: 'absolute' },
// 彩色环
circleArc: { width: size, height: size, borderColor: color,
borderRadius: size / 2, borderWidth: width },
两个 halfCircle 各自被 rotate 从 0deg 转到 180deg,第二个在进度过半(50)时淡入(interpolate([50, 50.01], [0, 1]))——于是:
- 进度 0→50%:第一个半圆转 0°→180°;
- 进度 50→100%:第二个半圆接着转(并淡入)。
⇒ 不需要 SVG、不需要 Canvas,只有 View + border + overflow:'hidden' + rotate ✓
★★★ 它的数字是可以精确预言的
源码里两行把"进度"和"标签"绑在一起:
animateTo((progress / max) * 100); // 动画目标
setLabelValue(Math.floor((value / 100) * max)); // 标签值
⇒ 把目标代回标签公式:floor((((progress/max)*100)/100)*max) = progress
⇒ 终值恒等于 progress(与 max 无关)—— 我用纯算术核对了 4 组,然后在设备上读标签文字验证:
| 实例 | 配置 | 实测读到的标签 |
|---|---|---|
| W1 | progress=75, max=100 | 75%(还带了 subtitle = 加载中)✓ |
| W2 | progress=50, **max=200** | 50(不是 25,也不是 100)✓ |
| W3 | progress=90, animateFromValue=30 | 90 ✓ |
| W4 | 不给 showProgressLabel | 没有标签 ✓ |
★★★★ 圆弧的角度同样可以定量核对
判据:把弧色设成红色、底环设成灰色,然后数红色像素。
用一个 progress={100} 的实例(画满整圈)当分母,progress={75} 当分子:
W0(progress=100,整圈)= 34255 个红像素
W1(progress=75) = 26682 个红像素
(减去 y≈2158 处一个不属于该轮的杂散红色聚类 983 个)
= 25699
★ W1 / W0 = 25699 / 34255 = 0.750 ← 预期 0.75
⇒ 实测比例 0.750,与 progress=75 精确一致 ✓✓
★★ 一个我原本怀疑、但被实测推翻的点
源码里动画是这么起的:
Animated.timing(animatedVal, { toValue, duration, easing, delay, useNativeDriver: true })
用了原生驱动,而标签却靠 JS 侧的 addListener 更新:
if (showProgressLabel) {
animatedVal.addListener(({value}) => setLabelValue(Math.floor((value / 100) * max)));
}
我原本怀疑"原生驱动下 JS 监听器收不到更新 ⇒ 标签会一直停在 0"。实测结果:标签正常更新到了终值(75% / 50 / 90 全部正确)⇒ 这个疑点在 RNOH 上不成立 ✓
另外三个实例的 onAnimationComplete 也都回调了:
DONE W1 status=true / DONE W2 status=true / DONE W3 status=true
设备侧断言 16 / 16 全部通过(A 10 + B 3 + C 3)。

一、先说结论
| 项 | 结果 |
|---|---|
| 上游最新版 | 2.1.0;license: ISC;上游 star 260 |
| 库类型 | 纯 JS 函数组件(dist/index.js 9345 B,编译产物,交付包不含 src/) |
| 是否需要原生适配 | 不需要(无 harmony/、无 NativeModules、不依赖任何 SVG 库) |
| 运行时依赖 | ✅ 零(dependencies 与 peerDependencies 都是空) |
prepare / postinstall | ✅ 都没有 |
main | dist/index.js |
| 需要 HAR / 权限 / ohpm | ❌ 都不需要 |
| ★ 是否可用 | ✅ 可以直接用(零老 API、零平台分支) |
| 设备侧断言 | ✅ 16 / 16 通过(A 10 + B 3 + C 3) |
| ★★★ 数字预言 | 标签终值 恒等于 progress ⇒ 实测 75% / 50(max=200)/ 90 / 无标签 ✓ |
| ★★★★ 角度定量 | 红像素比例 W1/W0 = 0.750,与 progress=75 精确一致 ✓ |
| ★★ 原生驱动疑点 | useNativeDriver: true + JS addListener ⇒ 实测标签仍正常更新(疑点不成立)✓ |
| ★ 画圆方式 | 两个半圆 + overflow:'hidden' + rotate 0deg→180deg,无 SVG |
| ★ 动画回调 | onAnimationComplete 三个实例都收到 status=true ✓ |
二、判定与接入
| 步 | 做法 | 结果 |
|---|---|---|
| ① | package.json 里有没有 harmony.autolinking | 没有 |
| ② | 仓库里有没有 harmony/ | 没有 |
| ③ | 代码里有没有 NativeModules | 全无 |
| ④ | import 图体检 | ✅ 只有 react + react-native ⇒ 零依赖(注意:它不依赖任何 SVG 库) |
| ⑤ | scripts 扫 prepare 家族 | ✅ 没有 |
| ⑥ | ★ 老 API 预扫(含垫片 / 从 react 取具名导入) | ✅ 全部为空 |
| ⑦ | ★ 平台分支预扫 | ✅ 空 |
| ⑧ | 原生痕迹预扫 | ✅ 空 |
npm install "git+https://atomgit.com/oh-react-native/react-native-progress-wheel.git#2.1.0-ohos-1.0.0" --ignore-scripts
装完即可用(本工程实测:bundle exit=0 + hvigor exit=0 + 页面正常渲染、标签与弧角都对)。
三、参数与默认值(从编译产物里读出来的)
const AnimatedProgressWheel = ({
animateFromValue = -1, // ≥0 时先把值设到这里,再动画
progress = 75, // 进度(终值就是它)
duration = 600, // 动画时长
onAnimationComplete, // 回调,收到 {finished} 之类
size, // 直径(无默认,必传)
width, // 环宽(无默认,必传)
color = 'white', // 弧色
rotation = '0deg', // 整体旋转
subtitle = '', // 标签下方的小字
delay = 0, // 起播延迟
max = 100, // 量程(只影响"动画目标",终值仍是 progress)
rounded = false, // 圆头端点
showProgressLabel = false, // 是否显示中间的数字
showPercentageSymbol = false, // 数字后是否加 %
backgroundColor = 'grey', // 底环色
labelStyle = {}, subtitleStyle = {},
easing, containerColor, // 内圆填充色
}) => { … }
几何关系:
container.size = size (同时 borderRadius = size/2)
borderWidth = width
背景环 / 彩弧的直径 = size
⇒ 想让环看起来"细一点",调 width;想改内圆底色,传 containerColor。
四、实测结果
4.1 断言 16 / 16
| 组 | 内容 | 结果 |
|---|---|---|
| A 契约与公式 | 默认导出是函数组件(无 prototype.render)、只有 default 一个具名键、标签公式 4 组纯算术核对、几何预言、原生驱动疑点记录 | 10/10 |
| B 渲染与几何 | 4 个实例渲染未抛错、标题安全区(vp 口径 ≥39)、判据说明 | 3/3 |
| C 行为与边界 | onAnimationComplete 回调、4 条边界记录 | 3/3 |
| 合计 | 16/16 |
4.2 标签数字(读 dump)
Text#Paragraph(158) [602,860,718,930] "75%" ← W1(progress=75, max=100)
Text#Paragraph(162) [570,930,750,1000] "加载中" ← W1 的 subtitle
Text#Paragraph(206) [625,1569,695,1639] "50" ← W2(progress=50, max=200)
Text#Paragraph(250) [625,2213,695,2232] "90" ← W3(animateFromValue=30, progress=90)
W4:该区没有任何标签文字 ✓
⇒ 终值恒等于 progress 被四个实例同时印证 ✓
4.3 圆弧角度(像素)
W0(progress=100,整圈)= 34255 个红像素
W1(progress=75) = 26682 个红像素(含一个不属于该轮的杂散聚类 983 个)
⇒ 修正后 25699
★ W1 / W0 = 25699 / 34255 = 0.750 ← 预期 0.75
⚠️ 方法上的一个细节:一开始我按 y 区间粗分两组,得到
0.779(偏离预期)。
逐行统计后发现 y≈2158 起还有一小撮红色(983 个像素),那是另一个元素(不是 W1 的轮),把它剔除后比例正好是 0.750 ✓
⇒ 教训:按颜色做定量统计时,先看"聚类是否单一" —— 页面上别处的同色元素会污染分母/分子。
4.4 动画回调
DONE W1 status=true DONE W2 status=true DONE W3 status=true
⇒ 三个实例的动画都跑完并回调 ✓



五、已知限制与本次验证边界
5.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
★ size 与 width 没有默认值 | 必须传,否则布局为 0 |
★ max 只影响动画目标 | 标签终值始终是 progress(max 会约掉) |
★ showProgressLabel 为真才建监听器 | 为假时不渲染标签、也不订阅数值 |
★ animateFromValue >= 0 时先从该值起步 | 想让动画从"当前值"继续时有用 |
★ 用了 useNativeDriver: true | 实测本机 RNOH 上 addListener 仍能收到更新(标签正常) |
★ 圆角端点要开 rounded | 会额外渲染三个圆头元素 |
★ rotation 是外层 transform | 布局 dump 不反映,只能看像素 |
★ 底环默认是 grey | 上深色背景时记得显式传 backgroundColor / containerColor |
5.2 本次验证的边界
rounded的圆头端点未做像素核对(页面上没启用该实例);rotation未验证(只从源码知道它是外层 transform);easing/delay未验证(用默认值跑的);duration只用了 600 / 1500 与 0 三种(没做"时长是否精确"的计时);W3的"起点是 30 而不是 0"未做逐帧核对(只确认了终值 90);labelStyle/subtitleStyle的样式未逐项核对;showPercentageSymbol只在 W1 上试过(75%✓);- 极小/极大
progress(0、max、超范围)未验证; onAnimationComplete的status字段只读了finished;- 未验证多实例同时动画的帧率表现;
- 只在一台模拟器上验证(
Pura X View,density 3,1320×2232),没有真机;未做跨平台对照。
未改动库代码。
六、常见问题
Q1:它需要装 SVG 库吗?
不需要。 import 图里只有 react 和 react-native —— 它用两个半圆 + overflow:'hidden' + rotate 拼出圆弧:
container: { width: size, height: size, borderRadius: size/2, overflow: 'hidden' },
halfCircle: { width: size, height: size/2, overflow: 'hidden', position: 'absolute' },
circleArc: { width: size, height: size, borderColor: color, borderRadius: size/2, borderWidth: width },
Q2:max 是干什么的?为什么 max=200, progress=50 显示的是 50?
因为公式是:
animateTo((progress / max) * 100); // 目标百分比
labelValue = Math.floor((value / 100) * max); // 百分比换算回 max 量纲
代进去 max 会约掉 ⇒ 终值永远是 progress。max 真正影响的是动画过程的百分比映射。
Q3:progress 变了会重新动画吗?
会 —— 源码里用 useEffect 依赖 [animateFromValue, progress, max],变化时重新 animateTo。
Q4:为什么我的环是白色的看不清?
color 默认是 'white'、backgroundColor 默认 'grey'。换到浅色/深色背景时记得显式传:
<AnimatedProgressWheel size={120} width={12} progress={75}
color="#D0021B" backgroundColor="#CCCCCC" containerColor="#FFFFFF" />
Q5:想看中间的数字怎么办?
同时给 showProgressLabel(显示数字);想带百分号再加 showPercentageSymbol;想在下面加一行小字用 subtitle。
Q6:圆角端点?
rounded(会额外渲染三个圆头元素:起点圆头、旋转遮罩、终点圆头)。
Q7:动画结束能收到通知吗?
用 onAnimationComplete:
<AnimatedProgressWheel … onAnimationComplete={status => console.log(status)} />
实测三个实例都收到了 status=true。
Q8:这个库需要鸿蒙侧做什么特殊处理吗?
不需要。 纯 JS、零依赖、无原生代码、无平台分支 —— 直接装、直接用 ✓
小结
react-native-progress-wheel 是纯 JS 的环形进度轮(零依赖、不依赖 SVG)。设备侧 16 / 16 断言通过,装完即可用。
最值得记下来的是这几条:
-
★★★ 它的数字能精确预言,而且实测完全吻合。
两行公式:animateTo((progress/max)*100)与labelValue = floor((value/100)*max)
⇒ 代进去 终值恒等于progress。实测四个实例:75%/50(max=200)/90(animateFromValue=30)/ 无标签 ✓ -
★★★★ 圆弧角度可以用"红像素比例"定量核对。
用progress=100的实例当分母、progress=75当分子 ⇒ 25699 / 34255 = 0.750,与预期 0.75 精确一致 ✓
⇒ 比"看起来像 3/4 圈"硬得多。 -
★ 颜色定量统计要先确认"聚类是否单一"。
第一版粗分 y 区间得到0.779(偏离);逐行看发现 y≈2158 处有 983 个不属于该轮的红色像素,剔除后正好0.750✓
⇒ 别处的同色元素会污染分子/分母。 -
★★ 一个我原本怀疑、实测被推翻的点。
Animated.timing(..., {useNativeDriver: true})配 JS 侧addListener—— 我担心"原生驱动下监听器收不到更新、标签停在 0"。
实测:标签正常更新到终值(75%/50/90全对),onAnimationComplete也都回调了status=true✓
⇒ 记录为"疑点不成立",而不是"我猜它有问题"。 -
★ 预检就把这个库判成"能用"。 老 API 预扫为空、平台分支为空、原生痕迹为空、依赖为空,且 import 图里没有 SVG ⇒ 接入成本接近零 ✓
-
★ 交付包只有编译产物(
dist/),没有src/。 本轮的源码结论全部来自dist/index.js(编译产物)—— 可读,但可读性不如 TypeScript 源码,这是交付包可以改进的一点。
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-progress-wheel(上游 2.1.0 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-progress-wheel |
| 适配 TAG | 2.1.0-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS、零依赖) |
| 库类型 | 纯 JS 函数组件:环形进度轮(无 SVG) |
| 运行时依赖 | ✅ 零 |
prepare / postinstall | ✅ 都没有 |
| 许可证 | ISC |
| 上游仓库 | https://github.com/RobertFOConnor/react-native-progress-wheel(★260) |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = ProgressWheelTestApp) |
# 从适配仓库安装(零依赖)
npm install "git+https://atomgit.com/oh-react-native/react-native-progress-wheel.git#2.1.0-ohos-1.0.0" --ignore-scripts
# 本地目录接入
npm install ../react-native-progress-wheel --ignore-scripts
import AnimatedProgressWheel from 'react-native-progress-wheel';
// ① 基本用法(size / width 必传)
<AnimatedProgressWheel
size={120} // 直径
width={12} // 环宽
progress={75} // 进度(终值就是它)
max={100}
color="#D0021B" // 弧色
backgroundColor="#CCCCCC" // 底环色
containerColor="#FFFFFF" // 内圆底色
duration={600}
rotation="0deg"
onAnimationComplete={(status) => console.log(status)}
/>
// ② 中间显示数字(+ 百分号 + 小字)
<AnimatedProgressWheel size={120} width={12} progress={75}
showProgressLabel showPercentageSymbol subtitle="加载中" … />
// ③ 从某个值开始动画
<AnimatedProgressWheel size={120} width={12} progress={90} animateFromValue={30} … />
// ④ 圆角端点
<AnimatedProgressWheel size={120} width={12} progress={60} rounded … />
// ⚠️ size / width 无默认值;max 只影响动画目标(标签终值恒为 progress)
// ⚠️ color 默认 'white'、backgroundColor 默认 'grey' —— 换背景时记得显式传
// ⚠️ 它不依赖任何 SVG 库(两个半圆 + overflow:hidden + rotate 拼出圆弧)
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey ProgressWheelTestApp
判据的复现方式
# ① 数字:标签终值恒等于 progress ⇒ 直接读轮内的 Text 节点
devecocli ui layout | Select-String 'Text#Paragraph' # ⇒ "75%" / "50" / "90"
# ② 角度:用一个 progress=100 的实例当分母,数红像素做比值
# 弧色 #D0021B、底环 #CCCCCC、内圆 #FFFFFF ⇒ 统计红色像素
# ★ 先确认聚类单一:别处的同色元素会污染分子/分母
# ③ 回调:grep 日志里的 onAnimationComplete
hdc shell "hilog -x | grep pwheel-test" | Select-String 'DONE'
环境搭建
本手册的验证环境是 React Native for OpenHarmony(RNOH)。从零准备开发环境(DevEco Studio、HarmonyOS SDK API 26、Node.js、ohpm、hvigorw、模拟器与 hdc 调试)请按 CPF-RN 开发者指南的「环境初始化」操作:
- CPF-RN 开发者指南 · 02-搭建准备 · 环境初始化:https://atomgit.com/CPF-RN/docs/blob/main/开发者指南/02-搭建准备/环境初始化.md
环境就绪后,本次验证使用的具体版本组合见文末「验证环境」一节。
验证环境
| 项 | 版本 |
|---|---|
| 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 / npm | v24.14.0 / 11.9.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 |
| 本机状态栏高度 | 117px = 39vp |
| 宿主依赖 | ✅ 无需补装(库零依赖) |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap 3 分 46 秒 ⇒ 一次成功、16/16(打包与构建的退出码都检查了) |
| 验证规模 | 设备侧 16 / 16 断言;标签数字 4 个实例(含无标签对照);像素比例 2 组(34255 vs 25699 ⇒ 0.750);动画回调 3 次(status=true);公式纯算术核对 4 组;4 张截图互异 |
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| React Native 鸿蒙官方社区(CPF-RN) | https://atomgit.com/CPF-RN |
| RNOH 框架仓库 | https://atomgit.com/openharmony-RN/ohos_react_native |
| RNOH 三方库使用文档 | https://atomgit.com/OpenHarmony-RN/usage-docs |
| React Native for OpenHarmony 组织 | https://atomgit.com/oh-react-native |
| RN 三方库鸿蒙适配清单 | https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview |
欢迎加入 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)