本文记录把 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 组,然后在设备上读标签文字验证:

实例配置实测读到的标签
W1progress=75, max=10075%(还带了 subtitle = 加载中)✓
W2progress=50, **max=200**50(不是 25,也不是 100)✓
W3progress=90, animateFromValue=3090 ✓
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✅ 都没有
maindist/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 本次验证的边界

  1. rounded 的圆头端点未做像素核对(页面上没启用该实例);
  2. rotation 未验证(只从源码知道它是外层 transform);
  3. easing / delay 未验证(用默认值跑的);
  4. duration 只用了 600 / 1500 与 0 三种(没做"时长是否精确"的计时);
  5. W3 的"起点是 30 而不是 0"未做逐帧核对(只确认了终值 90);
  6. labelStyle / subtitleStyle 的样式未逐项核对;
  7. showPercentageSymbol 只在 W1 上试过(75% ✓);
  8. 极小/极大 progress(0、max、超范围)未验证;
  9. onAnimationComplete 的 status 字段只读了 finished;
  10. 未验证多实例同时动画的帧率表现;
  11. 只在一台模拟器上验证(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 断言通过,装完即可用。

最值得记下来的是这几条:

  1. ★★★ 它的数字能精确预言,而且实测完全吻合。
    两行公式:animateTo((progress/max)*100) 与 labelValue = floor((value/100)*max)
    ⇒ 代进去 终值恒等于 progress。实测四个实例:75% / 50(max=200)/ 90(animateFromValue=30)/ 无标签 ✓

  2. ★★★★ 圆弧角度可以用"红像素比例"定量核对。
    用 progress=100 的实例当分母、progress=75 当分子 ⇒ 25699 / 34255 = 0.750,与预期 0.75 精确一致 ✓
    ⇒ 比"看起来像 3/4 圈"硬得多。

  3. ★ 颜色定量统计要先确认"聚类是否单一"。
    第一版粗分 y 区间得到 0.779(偏离);逐行看发现 y≈2158 处有 983 个不属于该轮的红色像素,剔除后正好 0.750 ✓
    ⇒ 别处的同色元素会污染分子/分母。

  4. ★★ 一个我原本怀疑、实测被推翻的点。
    Animated.timing(..., {useNativeDriver: true}) 配 JS 侧 addListener —— 我担心"原生驱动下监听器收不到更新、标签停在 0"。
    实测:标签正常更新到终值(75% / 50 / 90 全对),onAnimationComplete 也都回调了 status=true ✓
    ⇒ 记录为"疑点不成立",而不是"我猜它有问题"。

  5. ★ 预检就把这个库判成"能用"。 老 API 预扫为空、平台分支为空、原生痕迹为空、依赖为空,且 import 图里没有 SVG ⇒ 接入成本接近零 ✓

  6. ★ 交付包只有编译产物(dist/),没有 src/。 本轮的源码结论全部来自 dist/index.js(编译产物)—— 可读,但可读性不如 TypeScript 源码,这是交付包可以改进的一点。


本篇用到的库

项内容
三方库react-native-progress-wheel(上游 2.1.0 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-progress-wheel
适配 TAG2.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 Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npmv24.14.0 / 11.9.0
DevEco Studio26.0.0.621
HarmonyOS SDKAPI 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐