React Native for OpenHarmony 实战:三方库 react-native-animated-pagination-dots 的鸿蒙化适配指南
本文记录把 react-native-animated-pagination-dots(FlatList 分页指示点)适配到 HarmonyOS 的完整过程。
它做的事很专一:给一个横向 FlatList/ScrollView 画一排"当前在第几页"的小圆点,并提供 5 种点样式。
结论:这个库本身非常干净 —— 纯 JS、5 个函数组件、没有一处已移除的写法(连字符串 ref、mixins、Platform.OS 分支都没有)。但它有一个交付包没有声明的原生前置,值得单独说清楚。
★★★★ 一、最值得记的一件事:react-native-svg 这个前置"藏"在入口里
库导出 5 个组件:
ScalingDot / ExpandingDot / SlidingBorder / SlidingDot / LiquidLike
其中只有 LiquidLike 用到 SVG:
// src/dots/LiquidLike.tsx
import { Line, Svg } from 'react-native-svg'; // ★ 只有这一个文件
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
另外 4 个完全不用 svg(只靠 Animated + View)。看起来"只用那 4 个就不需要 svg"——但实际不行:
// src/index.tsx —— 入口无条件 import 了全部 5 个
import ScalingDot from './dots/ScalingDot';
import ExpandingDot from './dots/ExpandingDot';
import SlidingDot from './dots/SlidingDot';
import SlidingBorder from './dots/SlidingBorder';
import LiquidLike from './dots/LiquidLike'; // ★ 这一行就够了
export { ScalingDot, ExpandingDot, SlidingBorder, SlidingDot, LiquidLike };
实测(不装 svg 直接打包):
error Unable to resolve module react-native-svg from
…/src/dots/LiquidLike.tsx: react-native-svg could not be found …
bundle exit = 1
⇒ Metro 必须解析整个模块图 ⇒ 只要入口 import 了 LiquidLike,react-native-svg 就是硬前置 ✓
⇒ “5 个组件里 4 个不需要 svg” 在打包层面是无效的 —— 这条结论值得所有做三方库适配的人知道 ✓
⚠️ 而交付包的 README.OpenHarmony_CN.md 写的是:
基于
react-native-animated-pagination-dots@0.1.73的 React Native for OpenHarmony 纯 JavaScript 交付。保留上游公开入口,不增加 HAR、原生模块或运行时权限。
⇒ "纯 JavaScript / 不增加原生模块"这句话与 react-native-svg 这个真实前置不符 —— 交付包没有声明这个前置,使用者按它接入会直接打包失败 ✓(记为本轮发现的交付包缺陷)
好消息:鸿蒙版 svg 是存在的 —— @react-native-ohos/react-native-svg(15.13.0,43 个版本)✓ 只是它是原生库,需要接入 HAR 与包注册,属于另一类工作量。
★★★★ 我用"给原生依赖打桩"把纯 JS 部分隔离出来验证
本轮的目标是验证这个库的 JS 行为;而 LiquidLike 需要真 svg。最小侵入的做法:在 Metro 层把 react-native-svg 指向一个空桩,让模块图可解析、打包通过,而测试页不渲染 LiquidLike。
// metro.config.js 的 resolver 段里
extraNodeModules: {
react: path.resolve(__dirname, 'node_modules/react'),
'react-native': path.resolve(__dirname, 'node_modules/react-native'),
// ★ 仅用于隔离验证:把原生库 react-native-svg 指向空桩
'react-native-svg': path.resolve(__dirname, 'stubs/react-native-svg.js'),
},
空桩就是一个"任何具名导出都返回 View"的 Proxy:
// stubs/react-native-svg.js
const makeStub = (name) => {
const C = (props) => React.createElement(View, props);
C.displayName = `SvgStub(${name})`;
return C;
};
module.exports = new Proxy({}, {
get: (_t, prop) => (prop === '__esModule' ? true
: typeof prop === 'symbol' ? undefined
: makeStub(String(prop))),
});
⚠️ 打桩时我踩的一个坑(值得记)
我第一版把这段映射追加到了 metro.config.js 的文件末尾:
module.exports = mergeConfig(getDefaultConfig(__dirname), createHarmonyMetroConfig({…}), config);
// ↓ 我插在了这之后
config.resolver.extraNodeModules = {…, 'react-native-svg': …};
⇒ 打包依然报 Unable to resolve module react-native-svg ✗
原因:mergeConfig(...) 已经把 config 拷贝合并完了;之后再改原始的 config 对象对合并结果没有任何影响 ✓
修法:把映射放进 config.resolver.extraNodeModules 原始定义里(即 mergeConfig 之前)✓ —— 放对位置后打包立刻通过 ✓
教训:Metro 配置里凡是"在
module.exports = mergeConfig(…)之后再改config"的写法都是无效的;所有覆盖都必须写在传给mergeConfig的对象里。
★★★★ 二、这个库的判据形态:scrollX 是"可编程输入"
5 个组件的属性都有一个共同点(见 src/dots/types/index.ts):
interface ScalingDotProps { data: Array<Object>; scrollX: Animated.Value; … }
interface ExpandingDotProps { data: Array<Object>; scrollX: Animated.Value; … }
interface SlidingDotProps { data: Array<Object>; scrollX: Animated.Value; … }
⇒ scrollX 是外部传进来的一個 Animated.Value(实际使用中由 FlatList 的 onScroll 驱动)
⇒ ⇒ 验证时我可以自己 new Animated.Value(0),然后用 setValue() 精确驱动它 ✓✓
⇒ 不需要滑动、不需要点击、不需要输入 —— 这是最稳的判据形态之一 ✓
分相位驱动(W = useWindowDimensions().width = 440,因为库内部的 inputRange 用的就是它):
阶段 0:scrollX = 0 (第 1 页活跃)
阶段 1:scrollX = W = 440 (第 2 页活跃)
阶段 2:scrollX = 2W = 880 (第 3 页活跃)
阶段 3:scrollX = −W (左越界)
阶段 4:scrollX = 99999 (右越界)
每阶段停 7 秒,页面上打出阶段号 ⇒ 采集端在每个阶段 dump 一次即可精确核对(即使时序漂移也能靠阶段号对上)✓
★★★★★ 三、实测结果:ExpandingDot 的活跃点宽度精确转移
源码(ExpandingDot):
const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
const expand = scrollX.interpolate({
inputRange,
outputRange: [defaultProps.dotWidth, defaultProps.expandingDotWidth, defaultProps.dotWidth],
extrapolate: 'clamp',
}); // dotWidth 默认 10、expandingDotWidth 默认 20
<Animated.View style={[styles.dotStyle, dotStyle, { width: expand }, …]} />
// styles.dotStyle = { width: 10, height: 10, borderRadius: 5, marginHorizontal: 5 }
⇒ 活跃点的 width 在 10vp ↔ 20vp 之间插值 ⇒ 布局可见 ⇒ dump 直接可量 ✓✓
(density 3 ⇒ 非活跃 30px、活跃 60px)
实测(data 3 项,y=1752 那一行的三个点,按 x 排序):
| 阶段 | scrollX | 三个点的宽度 | 判定 |
|---|---|---|---|
| 0 | 0 | 60, 30, 30 | ✅ 第 1 个点被放大 ✓ |
| 1 | 440 | 30, 60, 30 | ✅ 第 2 个点被放大 ✓ |
| 2 | 880 | 30, 30, 60 | ✅ 第 3 个点被放大 ✓ |
| 3 | −440 | 30, 30, 30 | ✅(见下:这是正确行为) |
| 4 | 99999 | 30, 30, 30 | ✅(同上) |
⇒ 活跃点随 scrollX 精确转移到对应索引,宽度精确为 20vp = 60px ✓✓
★★★ 阶段 3/4 我一开始"预言错了",而库是对的
我原以为"越界 ⇒ clamp ⇒ 会停在第 1(或第 3)页、那个点被放大"。这个预期是错的,源码写得很清楚:
outputRange: [dotWidth, expandingDotWidth, dotWidth]
// ①非活跃 ②活跃(中间) ③非活跃
⇒ inputRange 的【两个端点】的输出是 dotWidth(非活跃) ⇒ 所以 scrollX 恰好落在端点、或者越界被 clamp 到端点时,那个点反而【不是】放大的 ✓
逐点核对阶段 4(scrollX = 99999):
| 点 | inputRange | clamp 到 | 输出 | 实测 |
|---|---|---|---|---|
| dot0 | [−440, 0, 440] | 440 | dotWidth = 10vp = 30px | ✅ 30 |
| dot1 | [0, 440, 880] | 880 | dotWidth = 30px | ✅ 30 |
| dot2 | [440, 880, 1320] | 1320 | dotWidth = 30px | ✅ 30 |
⇒ 实测 30,30,30 与源码推导完全一致 ✓✓
教训:
interpolate的 “第三段/端点输出” 必须回源码看 —— 凭"越界会钳到某页"的直觉写断言,会写出看起来失败、其实是自己预期错的判据。这类"自定义插值"的组件,预言前一定要把outputRange三个值逐个看清。
★★★★ 四、一个天然的对照组:同一份 scrollX,两种实现方式
库里有两种"让活跃点突出"的做法:
| 组件 | 手段 | 布局可见 | dump 可量 |
|---|---|---|---|
ExpandingDot | width 插值 10 → 20vp | ✅ 是 | ✅ 活跃点 60px / 其余 30px |
ScalingDot | transform: [{ scale }] 1 → 1.4 | ❌ 否 | ❌ 宽度全程恒为 30px |
实测对照(同一时刻的 dump,两个组件上下相邻):
y=1752(ExpandingDot)⇒ 阶段 0/1/2 分别是 60,30,30 / 30,60,30 / 30,30,60
y=1872(ScalingDot) ⇒ 全程 30, 30, 30 ← ★ 三个点始终存在且宽度不变
⇒ 完美印证:transform 不参与布局 ⇒ 用 scale 做的放大在布局 dump 里完全看不到 ✓✓
⇒ 实践含义:要验证"缩放类"动效,必须走像素,不能指望布局;而要验证"宽度类"动效,dump 就是最硬的证据。
★★★ 五、固定尺寸组件的实测
| 组件 | 源码常量 | 预言(density 3) | 实测 |
|---|---|---|---|
SlidingDot 的点 | dotSize = 12 | 36px | ✅ y=1986 ⇒ 36, 36, 36 |
SlidingBorder 的点 | dotSize = 24,点 = dotSize/2 = 12 | 36px | ✅ y=2088 ⇒ 36, 36, 36 |
⚠️ SlidingDot 的指示器 | dotSize = 12 | 36px | ❌ 未量到 |
⚠️ SlidingBorder 的指示器 / 点容器 | dotSize + borderPadding = 19 | 57px | ❌ 未量到 |
⚠️ 两个"指示器"没量到:它们是 position: 'absolute' + zIndex: 99 的独立层,没有出现在常规布局框里 ⇒ 本轮如实记为未量到(它们的位置/尺寸变化需要像素判据)。
★★ 六、契约与渲染
PASS | ScalingDot 已导出且是函数组件 | 期望=function | 实际=function
PASS | ExpandingDot 已导出且是函数组件
PASS | SlidingBorder 已导出且是函数组件
PASS | SlidingDot 已导出且是函数组件
PASS | LiquidLike 已导出且是函数组件
PASS | ★ 恰好 5 个具名导出 | 期望=5 | 实际=5
PASS | ★ ExpandingDot 是函数组件(无 prototype.render) | 期望=undefined | 实际=undefined
PASS | ★ ScalingDot / SlidingDot / SlidingBorder 均为函数组件
NOTE | useWindowDimensions().width = 440
NOTE | Dimensions.get("window") = {"width":440,"height":744,"scale":3,"fontScale":1}
PASS | 主标题窗口 y(vp) ≥ 39(状态栏 117px=39vp) | 实际=true
⇒ 5 个导出全是函数组件(prototype.render 为 undefined)✓;useWindowDimensions() 与 Dimensions.get('window') 都是 440×744 ✓




七、接入与预检
| 步 | 做法 | 结果 |
|---|---|---|
| ① | harmony.autolinking | 没有 |
| ② | harmony/ / ios/ / android/ | 都没有 |
| ③ | 原生痕迹预扫 | ✅ 无原生源文件、无 NativeModules |
| ④ | import 图体检 | react + react-native + react-native-svg ⚠️(只被 LiquidLike 使用,但入口无条件 import) |
| ⑤ | scripts 扫 prepare 家族 | ⚠️ 有:prepare = "bob build && husky install" ⇒ 必须 --ignore-scripts |
| ⑥ | ★ 老写法预扫(已扩到七项) | ✅✅ 全部为空:无 createClass、无 React.PropTypes、无字符串 ref/this.refs、无 mixins、无 componentWillReceiveProps、无 from 'react' 具名导入、无 Platform.OS 分支 |
| ⑦ | ★ 是否用 measure() 量布局 | ✅ 未使用(这是本轮特意加的一项排查) |
npm install "git+https://atomgit.com/oh-react-native/react-native-animated-pagination-dots.git#0.1.73-ohos-1.0.0" --ignore-scripts
# ⚠️ 但它还需要 react-native-svg(原生库);否则 Metro 会 Unable to resolve module
八、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | src/(TS 源码 7 个文件)+ lib/(commonjs + module + typescript)✓ |
spec.json | ⚠️ 缺 upstreamCommit |
validation | ⚠️ "build-pass-device-pending" |
| ⚠️ 声明的基线 | reactNative: "0.88.0" + rnoh: "0.84.4"(verification: evidence/batch50/…)⇒ 两字段自相矛盾(rnoh 0.84.4 是配 RN 0.84.x 的),且既不是当前宿主(0.84.1 / 0.84.3)也不是权威 0.88 基线(0.88.0-rc.1 + RNOH 0.88.0-rc.1) |
| ⚠️ 交付包文档缺陷 | README.OpenHarmony_CN.md 声称"纯 JavaScript 交付 / 不增加原生模块",但没提 react-native-svg 这个硬前置 ⇒ 按它接入会直接打包失败 |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。这个库没有任何已移除的写法(预扫七项全空),
它的行为只依赖 Animated 的 interpolate 与 View 布局 —— 这两者与 RN 小版本无关 ⇒ 本轮的结论跨基线成立。
九、已知限制与本次验证边界
9.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
★★★ react-native-svg 是硬前置 | 即使只用 4 个"不需要 svg"的组件也一样(入口无条件 import LiquidLike)⇒ 必须装鸿蒙版 @react-native-ohos/react-native-svg 或打桩 |
★ scrollX 由外部驱动 | 传入 Animated.Value,通常来自 FlatList 的 onScroll;页距用的是 useWindowDimensions().width |
★★ ExpandingDot 改 width | 布局可见 ⇒ dump 可量(活跃 20vp / 非活跃 10vp) |
★★ ScalingDot 改 transform: scale | 布局不可见 ⇒ 只能靠像素(实测宽度恒 10vp) |
★★ SlidingDot / SlidingBorder 的指示器是 absolute + zIndex:99 | 位移用 translateX ⇒ 布局 dump 里量不到,需像素判据 |
★ interpolate 的三段输出要看全 | [非活跃, 活跃, 非活跃] ⇒ 端点/越界处不放大(extrapolate: 'clamp') |
★ prepare = "bob build && husky install" | 安装请加 --ignore-scripts |
★ 容器都是 position:'absolute' | 若渲染在 ScrollView 内容里,会相对内容定位(量不到)⇒ 应放在滚动容器之外 |
9.2 本次验证的边界
- ★★
LiquidLike完全未验证(需要真 svg;本轮用空桩隔离,故意不渲染它); - ★ 三个"指示器"未量到(
SlidingDot36px /SlidingBorder57px)—— 它们是absolute + zIndex:99层,未出现在常规布局框里; - ★
ScalingDot的放大未做像素验证(只有"布局宽度不变"这一侧的证据); - ★
SlidingDot/SlidingBorder的translateX滑动位置未验证(同样需要像素); - 未验证
SlidingBorder的点容器宽度(预言 19vp = 57px); - 未验证
ExpandingDot的opacity与backgroundColor插值(dump 不反映颜色/透明度); - 未验证多页(>3 项)、单页(1 项)、空
data的边界; - 未验证
containerStyle/dotStyle的各类覆盖组合; - 未在
RN 0.88.0/RNOH 0.84.4上复验(交付包声明的基线); - 未测真机与其它屏幕尺寸(只验证了 440×744 一个尺寸);
- 未做跨平台对照。
未改动库代码(空桩只影响本工程的 Metro 解析,不改动库自身任何文件)。
十、常见问题
Q1:我只用 ExpandingDot,可以不装 react-native-svg 吗?
不行。 入口 src/index.tsx 无条件 import LiquidLike from './dots/LiquidLike',而 LiquidLike 第一行就 import { Line, Svg } from 'react-native-svg' ⇒ Metro 必须解析到它,否则:
Unable to resolve module react-native-svg from …/LiquidLike.tsx。
Q2:那怎么办?
两条路:
- 正式接入:装鸿蒙版
@react-native-ohos/react-native-svg(本机可查到的版本是15.13.0),并接入它的 HAR 与包注册; - 只想验证其余组件:在
metro.config.js的resolver.extraNodeModules(注意:必须写在传给mergeConfig的对象里)把react-native-svg指向一个空桩 ⇒ 打包可过,但LiquidLike不可用。
Q3:scrollX 我该传什么?
一个 Animated.Value,值 = 当前横向偏移(单位 px/vp 与你的页宽一致)。通常:
const scrollX = useRef(new Animated.Value(0)).current;
<FlatList
horizontal pagingEnabled
onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], {useNativeDriver: false})}
…
/>
<ExpandingDot data={data} scrollX={scrollX} expandingDotWidth={20} dotStyle={{width: 10, height: 10}} />
⚠️ 页距用的是 useWindowDimensions().width —— 如果你的页宽不是整屏宽,插值的对应关系就跟不上。
Q4:为什么我滑动到最后一页,最后一个点反而没有放大?
因为 ExpandingDot 的插值是:
outputRange: [dotWidth, expandingDotWidth, dotWidth]
// 非活跃 活跃 非活跃
inputRange 是 [(i-1)*W, i*W, (i+1)*W] ⇒ scrossX 恰好等于 i*W 时才是"活跃";
超过 (i+1)*W 就被 clamp 到端点,而端点的输出是 dotWidth(非活跃) ⇒ 所以越界不会让点放大。
Q5:怎么让点"看起来放大"但又不改变布局?
用 ScalingDot(transform: [{scale}])—— 它的放大不参与布局,所以不会把相邻点挤开。反过来,如果你希望相邻点被推开,就用 ExpandingDot(改 width)。
Q6:为什么我把指示点放在 ScrollView 里就量不到/看不到?
这些组件的容器都是 position: 'absolute' ⇒ 放在滚动容器的内容里时,它会相对内容定位。
⇒ 应把它们放在滚动容器【之外】(例如 <View style={{flex:1}}><ScrollView>…</ScrollView><ExpandingDot …/></View>)。
Q7:这个库需要鸿蒙侧做什么特殊处理吗?
库本身不需要(纯 JS、无平台分支、无原生代码)。
唯一的前置是 react-native-svg —— 那是"依赖的原生库"需要接入,而不是这个库要改。
小结
react-native-animated-pagination-dots 是纯 JS 的 5 个函数组件(预扫七项全空,写法非常现代)。但它有一个藏得很深的前置,也提供了一个非常适合验收的接口形态。
最值得记下来的是这几条:
-
★★★★ “5 个组件里 4 个不需要 svg” 在打包层面是无效的。
入口src/index.tsx无条件import LiquidLike,而它需要react-native-svg⇒ Metro 必须解析整个模块图 ⇒
实测不装 svg 直接Unable to resolve module react-native-svg … bundle exit = 1✓
⇒ 交付包文档却写着"纯 JavaScript 交付 / 不增加原生模块" ⇒ 这是交付包的缺陷,使用者按文档接入会直接失败。 -
★★★★
scrollX是可编程输入 ⇒ 判据不必依赖滑动/点击。
自己new Animated.Value(0),用setValue()分相位驱动(0 / W / 2W / −W / 99999),
每相位停住并打出相位号 ⇒ 采集端 dump 一次就能精确核对 ✓ —— 这是最稳的判据形态之一。 -
★★★★
ExpandingDot的活跃点宽度精确转移,且 dump 就是硬证据。
实测阶段 0/1/2 分别是60,30,30/30,60,30/30,30,60(活跃点 20vp = 60px)✓ -
★★★ 我"预言错"了一次,而库是对的 ——
interpolate的三段输出必须看全。
outputRange: [dotWidth, expandingDotWidth, dotWidth]⇒ 端点输出是"非活跃" ⇒ 越界 clamp 后不会放大。
逐点核对阶段 4:三个点分别 clamp 到 440 / 880 / 1320,输出都是dotWidth⇒ 实测30,30,30完全一致 ✓
⇒ 凭直觉写断言,会写出"看起来失败其实预期错"的判据。 -
★★★★ “改
width” 与 “改scale” 给出一个天然对照。
同一份scrollX、同一时刻的 dump:ExpandingDot(y=1752)在 60/30 之间切换,ScalingDot(y=1872)全程 30 ✓
⇒transform不参与布局 ⇒ 缩放类动效只能靠像素,宽度类动效 dump 就够 ✓ -
★★★ 给原生依赖打桩,可以把纯 JS 部分隔离出来验证。
在resolver.extraNodeModules里把react-native-svg指向空桩 ⇒ 打包通过、4 个组件可验证、LiquidLike如实标注未验证 ✓ -
★★ Metro 配置的一个硬规则(我踩了): 所有覆盖必须写在传给
mergeConfig的对象里 ——
module.exports = mergeConfig(…)之后再改config是无效的(mergeConfig已经拷贝合并完了)✗ -
★★ 判据逐条直写日志(
PASS|描述|期望|实际),采集端grep即得全证据 ⇒ 不要把证据留存绑在页面 UI 上。
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-animated-pagination-dots(上游 0.1.73 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-animated-pagination-dots |
| 适配 TAG | 0.1.73-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | ⚠️ 库本身不需要;但它硬依赖 react-native-svg ⇒ 该原生库需要 HAR 接入(或打桩隔离) |
| 库类型 | FlatList 分页指示点(5 个函数组件:ScalingDot / ExpandingDot / SlidingBorder / SlidingDot / LiquidLike) |
| 运行时依赖 | ✅ 无 dependencies;⚠️ peer 里有 react-native-svg,且入口无条件使用 |
prepare / postinstall | ⚠️ 有(bob build && husky install)⇒ 安装加 --ignore-scripts |
| 许可证 | MIT |
| 上游仓库 | https://github.com/weahforsage/react-native-animated-pagination-dots(★252) |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = PaginationDotsTestApp) |
# 从适配仓库安装
npm install "git+https://atomgit.com/oh-react-native/react-native-animated-pagination-dots.git#0.1.73-ohos-1.0.0" --ignore-scripts
# ⚠️ 还必须解决 react-native-svg(原生库),否则 Metro 报 Unable to resolve module
# 正式做法:npm install @react-native-ohos/react-native-svg 并接入其 HAR
# 隔离验证:在 metro.config.js 的 resolver.extraNodeModules 里把 'react-native-svg' 指向空桩
import {ExpandingDot, ScalingDot, SlidingDot, SlidingBorder} from 'react-native-animated-pagination-dots';
const scrollX = useRef(new Animated.Value(0)).current;
const data = [{}, {}, {}];
// ★ 放在滚动容器【之外】(容器都是 position:'absolute')
<View style={{flex: 1}}>
<FlatList
horizontal pagingEnabled data={data}
onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], {useNativeDriver: false})}
renderItem={() => <View style={{width: 440, height: 200}} />}
/>
{/* 活跃点靠【宽度】变化 ⇒ 布局可见 */}
<ExpandingDot data={data} scrollX={scrollX} expandingDotWidth={20}
dotStyle={{width: 10, height: 10, borderRadius: 5, marginHorizontal: 5}} />
{/* 活跃点靠【scale】变化 ⇒ 布局不可见(要验证只能看像素) */}
<ScalingDot data={data} scrollX={scrollX} activeDotScale={1.4} containerStyle={{bottom: 80}} />
<SlidingDot data={data} scrollX={scrollX} dotSize={12} containerStyle={{bottom: 40}} />
</View>
// ⚠️ 页距用的是 useWindowDimensions().width ⇒ 页宽不是整屏宽时插值对不上
// ⚠️ interpolate 的 outputRange 三段是 [非活跃, 活跃, 非活跃] ⇒ 端点/越界不放大
// ⚠️ LiquidLike 需要 react-native-svg(原生库);入口无条件 import,所以它是硬前置
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey PaginationDotsTestApp
判据的复现方式
# ① 分相位驱动:页面每 7 秒切一个 scrollX 并打出相位号
hdc shell "hilog -x | grep dots-test" | Select-String '===== 阶段|PASS|FAIL|SUMMARY'
# ② 量活跃点宽度(ExpandingDot):活跃 20vp=60px、非活跃 10vp=30px
devecocli ui layout | Select-String '\[(\d+),(\d+),(\d+),(\d+)\]' # 找 w=60 / w=30 的方块
# ③ 对照组(ScalingDot):全程 w=30 ⇒ transform 不参与布局
# ④ 固定尺寸:SlidingDot 的点 36px、SlidingBorder 的点 36px(= 12vp × 3)
# ⑤ 注意:指示器是 absolute + zIndex:99,出现在常规布局框之外
验证环境
| 项 | 版本 |
|---|---|
| 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 |
| 宿主依赖 | ⚠️ react-native-svg 用空桩隔离(本页不渲染 LiquidLike) |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | 首次因 react-native-svg 解析失败打包中止(未装机)⇒ 打桩后 assembleHap 4 分 18 秒 成功 |
| 验证规模 | 契约 11 条 PASS;5 个相位各 dump 1 次(活跃点宽度精确转移 ✓);对照组 1 组(ScalingDot 恒 30px);固定尺寸 2 组命中(36px/36px);4 张截图互异 |
⚠️ 基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成;
本库无任何已移除写法,行为只依赖Animated插值与View布局 ⇒ 结论跨基线成立。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| 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)