本文记录把 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三个点的宽度判定
0060, 30, 30✅ 第 1 个点被放大 ✓
144030, 60, 30✅ 第 2 个点被放大 ✓
288030, 30, 60✅ 第 3 个点被放大 ✓
3−44030, 30, 30✅(见下:这是正确行为)
49999930, 30, 30✅(同上)

⇒ 活跃点随 scrollX 精确转移到对应索引,宽度精确为 20vp = 60px ✓✓

★★★ 阶段 3/4 我一开始"预言错了",而库是对的

我原以为"越界 ⇒ clamp ⇒ 会停在第 1(或第 3)页、那个点被放大"。这个预期是错的,源码写得很清楚:

outputRange: [dotWidth, expandingDotWidth, dotWidth]
//              ①非活跃      ②活跃(中间)      ③非活跃

⇒ inputRange 的【两个端点】的输出是 dotWidth(非活跃) ⇒ 所以 scrollX 恰好落在端点、或者越界被 clamp 到端点时,那个点反而【不是】放大的 ✓

逐点核对阶段 4(scrollX = 99999):

点inputRangeclamp 到输出实测
dot0[−440, 0, 440]440dotWidth = 10vp = 30px✅ 30
dot1[0, 440, 880]880dotWidth = 30px✅ 30
dot2[440, 880, 1320]1320dotWidth = 30px✅ 30

⇒ 实测 30,30,30 与源码推导完全一致 ✓✓

教训:interpolate 的 “第三段/端点输出” 必须回源码看 —— 凭"越界会钳到某页"的直觉写断言,会写出看起来失败、其实是自己预期错的判据。这类"自定义插值"的组件,预言前一定要把 outputRange 三个值逐个看清。

★★★★ 四、一个天然的对照组:同一份 scrollX,两种实现方式

库里有两种"让活跃点突出"的做法:

组件手段布局可见dump 可量
ExpandingDotwidth 插值 10 → 20vp✅ 是✅ 活跃点 60px / 其余 30px
ScalingDottransform: [{ 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 = 1236px✅ y=1986 ⇒ 36, 36, 36
SlidingBorder 的点dotSize = 24,点 = dotSize/2 = 1236px✅ y=2088 ⇒ 36, 36, 36
⚠️ SlidingDot 的指示器dotSize = 1236px❌ 未量到
⚠️ SlidingBorder 的指示器 / 点容器dotSize + borderPadding = 1957px❌ 未量到

⚠️ 两个"指示器"没量到:它们是 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 本次验证的边界

  1. ★★ LiquidLike 完全未验证(需要真 svg;本轮用空桩隔离,故意不渲染它);
  2. ★ 三个"指示器"未量到(SlidingDot 36px / SlidingBorder 57px)—— 它们是 absolute + zIndex:99 层,未出现在常规布局框里;
  3. ★ ScalingDot 的放大未做像素验证(只有"布局宽度不变"这一侧的证据);
  4. ★ SlidingDot / SlidingBorder 的 translateX 滑动位置未验证(同样需要像素);
  5. 未验证 SlidingBorder 的点容器宽度(预言 19vp = 57px);
  6. 未验证 ExpandingDot 的 opacity 与 backgroundColor 插值(dump 不反映颜色/透明度);
  7. 未验证多页(>3 项)、单页(1 项)、空 data 的边界;
  8. 未验证 containerStyle / dotStyle 的各类覆盖组合;
  9. 未在 RN 0.88.0 / RNOH 0.84.4 上复验(交付包声明的基线);
  10. 未测真机与其它屏幕尺寸(只验证了 440×744 一个尺寸);
  11. 未做跨平台对照。

未改动库代码(空桩只影响本工程的 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 个函数组件(预扫七项全空,写法非常现代)。但它有一个藏得很深的前置,也提供了一个非常适合验收的接口形态。

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

  1. ★★★★ “5 个组件里 4 个不需要 svg” 在打包层面是无效的。
    入口 src/index.tsx 无条件 import LiquidLike,而它需要 react-native-svg ⇒ Metro 必须解析整个模块图 ⇒
    实测不装 svg 直接 Unable to resolve module react-native-svg … bundle exit = 1 ✓
    ⇒ 交付包文档却写着"纯 JavaScript 交付 / 不增加原生模块" ⇒ 这是交付包的缺陷,使用者按文档接入会直接失败。

  2. ★★★★ scrollX 是可编程输入 ⇒ 判据不必依赖滑动/点击。
    自己 new Animated.Value(0),用 setValue() 分相位驱动(0 / W / 2W / −W / 99999),
    每相位停住并打出相位号 ⇒ 采集端 dump 一次就能精确核对 ✓ —— 这是最稳的判据形态之一。

  3. ★★★★ ExpandingDot 的活跃点宽度精确转移,且 dump 就是硬证据。
    实测阶段 0/1/2 分别是 60,30,30 / 30,60,30 / 30,30,60(活跃点 20vp = 60px)✓

  4. ★★★ 我"预言错"了一次,而库是对的 —— interpolate 的三段输出必须看全。
    outputRange: [dotWidth, expandingDotWidth, dotWidth] ⇒ 端点输出是"非活跃" ⇒ 越界 clamp 后不会放大。
    逐点核对阶段 4:三个点分别 clamp 到 440 / 880 / 1320,输出都是 dotWidth ⇒ 实测 30,30,30 完全一致 ✓
    ⇒ 凭直觉写断言,会写出"看起来失败其实预期错"的判据。

  5. ★★★★ “改 width” 与 “改 scale” 给出一个天然对照。
    同一份 scrollX、同一时刻的 dump:ExpandingDot(y=1752)在 60/30 之间切换,ScalingDot(y=1872)全程 30 ✓
    ⇒ transform 不参与布局 ⇒ 缩放类动效只能靠像素,宽度类动效 dump 就够 ✓

  6. ★★★ 给原生依赖打桩,可以把纯 JS 部分隔离出来验证。
    在 resolver.extraNodeModules 里把 react-native-svg 指向空桩 ⇒ 打包通过、4 个组件可验证、LiquidLike 如实标注未验证 ✓

  7. ★★ Metro 配置的一个硬规则(我踩了): 所有覆盖必须写在传给 mergeConfig 的对象里 ——
    module.exports = mergeConfig(…) 之后再改 config 是无效的(mergeConfig 已经拷贝合并完了)✗

  8. ★★ 判据逐条直写日志(PASS|描述|期望|实际),采集端 grep 即得全证据 ⇒ 不要把证据留存绑在页面 UI 上。


本篇用到的库

项内容
三方库react-native-animated-pagination-dots(上游 0.1.73 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-animated-pagination-dots
适配 TAG0.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 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
宿主依赖⚠️ 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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐