本文记录把 react-native-collapsible(可折叠容器 + 手风琴)适配到 HarmonyOS 的完整过程。

这个库有两个特点让这轮验证特别"干净":

  1. 它动画的是普通布局高度(Animated + useNativeDriver: false + overflow: hidden),所以折叠高度可以被布局系统直接量出来,而不必靠像素比对;
  2. 纯 JS、零运行时依赖,接进宿主只需要三处改动,没有任何原生接线。

结果是:折叠/展开的高度逐像素精确,而且顺手挖出了两个很隐蔽的行为边界——"挂载时就展开"其实不生效,以及 align 在没测过高度之前完全无效。

在这里插入图片描述


一、先说结论

项结果
上游最新版1.6.2(npm gitHead = c79b7c01…,MIT)
是否需要原生适配不需要——纯 JS,无原生模块,零运行时依赖
与 npm 上游的差异7 / 8 个文件去掉 CR 后逐字符一致,唯一差异是 package.json;Collapsible.js / Accordion.js / 两个 .d.ts 实现零改动
自动链接信号linked 10 libraries, skipped 2 libraries(计数未增加 ✓)
编译assembleHap 4 分 22 秒;HAP 81,725,574 → 81,774,729 字节(+49,155 ≈ 48 KB)
设备侧断言✅ 34 / 34 全部通过
几何实测✅ 逐像素精确:collapsedHeight=40 → +120px;展开到内容 120vp → +360px;内容 90vp → +270px;手风琴章节展开 → +270px
动画✅ 中途采样 331(严格介于 0 与 360 之间)⇒ 是过渡,不是瞬变
发现的行为边界⚠️ ① 挂载时 collapsed={false} 高度锁 0;② align 在 contentHeight 为 0 时静默无效;③ align='bottom' 位移与预期差 47px
发现的类型问题⚠️ 两个组件的 .d.ts 与 React 19 类型不兼容(TS2607 / TS2786),运行时不受影响

一句话结论:可用,而且折叠高度精确到像素。但有三件事必须知道:Accordion 要走子路径导入、"默认展开"不能只写 collapsed={false}、align 需要先测过一次高度。


二、判定过程:纯 JS、零依赖,但有两个入口

判断一个 RN 库要不要做原生适配,固定走三步:

步做法这个库的结果
①package.json 里有没有 harmony.autolinking没有
②仓库里有没有 harmony/没有
③代码里有没有 NativeModules / requireNativeComponent没有

三步都指向"纯 JS,不需要原生适配"。而且这个库连运行时依赖都没有:

"dependencies": {},                      // 实际没有这个字段
"peerDependencies": {"react": "*", "react-native": "*"}

Animated 和 Easing 都来自 react-native 本身,没有任何第三方依赖 —— 这在三方库里其实少见,也意味着接入时不会遇到"依赖没被装上"这类问题。

2.1 但它有两个入口

package.json 的 main 是 Collapsible.js,而这个文件只导出 Collapsible 一个 default:

// Collapsible.js
export default class Collapsible extends Component { ... }

// Accordion.js  ← 同级另一个文件
export default class Accordion extends Component { ... }

⇒ Accordion 必须走子路径:

import Collapsible from 'react-native-collapsible';
import Accordion from 'react-native-collapsible/Accordion';   // ← 不能写成具名导入

写成 import {Accordion} from 'react-native-collapsible' 的话:

  • 运行时拿到的是 undefined(Metro 不做类型检查,不会报错,直到你渲染它才炸);
  • TypeScript 会报 TS2614: Module has no exported member 'Accordion'。

上游 README 的示例其实是写对的(正是上面这两行),所以这一条要看源码或上游文档,不能只看交付包的 README。而交付包的 README 里只写了 import Library from 'react-native-collapsible';,没提子路径。

2.2 两个组件都没有 propTypes

Collapsible.propTypes === undefined    // 实测
Accordion.propTypes   === undefined    // 实测

两个组件都只有 defaultProps,没有任何运行时属性校验。所以:

  • 属性名写错不会有开发期警告,只会静默不生效;
  • Accordion.activeSections 虽然在 propTypes 里没声明,但 render 里直接 activeSections.includes(...) ⇒ 不传会 TypeError,属于"事实上的必填"。

公开 API:

组件属性
Collapsiblealign(top/center/bottom,默认 top)、collapsed(默认 true)、collapsedHeight(默认 0)、duration(默认 300)、easing(默认 'easeOutCubic')、enablePointerEvents(默认 false)、renderChildrenCollapsed(默认 true)、style、onAnimationEnd、children
Accordionsections、activeSections(事实必填)、renderHeader、renderContent、onChange、expandMultiple、expandFromBottom、underlayColor、touchableComponent、touchableProps、renderSectionTitle、renderFooter、disabled、renderAsFlatList、keyExtractor(默认返回 index)、containerStyle、sectionContainerStyle、onAnimationEnd + 上表里除 children 外的 Collapsible 属性透传

三、这个交付包长什么样:实现零改动

与 react-native-collapsible@1.6.2 的 npm tarball 逐文件比对(先去 CR 再逐字符比较):

7 / 8 个文件完全一致(Collapsible.js、Accordion.js、index.d.ts、Accordion.d.ts、babel.config.js、README.md、LICENSE),唯一不同的是 package.json:

字段变化
description追加 " for React Native for OpenHarmony"
repository.url / homepage改为 AtomGit 地址
scripts.test新增 node --test __tests__/harmony-contract.test.cjs

实现文件零改动,所以下面所有结论都只涉及使用方式与行为边界,与适配质量无关。

交付包另外多了 6 个文件:双语 README、spec.json、代码检查报告、契约测试、.gitignore。


四、接入宿主与构建运行

纯 JS 库的接法只有三处:

// package.json
"react-native-collapsible": "file:../react-native-collapsible"
// metro.config.js —— file: 装进来是 junction,必须让 Metro 找得到源码
watchFolders: [path.resolve(__dirname, '../react-native-collapsible')],
// index.js
AppRegistry.registerComponent('CollapsibleTestApp', () => CollapsibleTestApp);

不要动 harmony/entry/oh-package.json5(不需要 HAR),不要动 module.json5(不需要权限)。信号是自动链接计数不增加:

info updated 4 file(s), linked 10 libraries, skipped 2 libraries
项数值
首次 assembleHap4 分 22 秒
二次重编(给测量区加了就地按钮)3 分 40 秒
HAP 变化81,725,574 → 81,774,729 字节(+49,155 ≈ 48 KB)

五、验证设计:动画的是"布局高度",所以可以直接量

5.1 为什么这轮不用像素比对

看这个库的核心实现:

this._animation = Animated.timing(this.state.height, {
  useNativeDriver: false,          // 高度不可原生驱动
  toValue: height ? height : 0,
  duration,
  easing,
}).start(...)

外层是 Animated.View,样式是 { overflow: 'hidden', height: <animated> }。

⇒ 它动画的是一个普通布局高度,而不是颜色、透明度或不透明度这类"只有像素才能看出来的东西"。所以布局系统给出的矩形就是精确的、定量的证据,不需要截图比对。

5.2 方法:marker 夹逼

页面在每个折叠区前后各放一个文本 marker:

MARK-A-START
〈就地按钮〉
[ Collapsible 折叠区 ]
MARK-A-END

于是:

Y(MARK-A-END) − Y(MARK-A-START) = 折叠区高度 + 固定装饰(marker 自身 + 按钮)

比较同一区域在不同状态下的间距差,就得到折叠区的高度增量 —— 这个差值与滚动位置、marker 高度、按钮高度全都无关,因此极其稳健。

. E:\rnoh-work\collapsible-probe.ps1
Measure-Gap -Top 'MARK-A-START' -Bottom 'MARK-A-END'   # 自动滚动到两个 marker 同时可见

一个实现细节:展开后页面会变长,MARK-A-END 可能滑出视口导致读数为空。所以探针里的 Scroll-Both 会反复滚动直到两个 marker 同时可见才读数。

5.3 内容高度刻意取整

页面里内容高度取整数 vp(120 / 90),设备 density = 3 ⇒ 期望像素高度 360 / 270 / 120。

这样"精确到像素"才有判据。 如果内容高度是 117 这种数,量出来 351 就只能说"差不多"。

5.4 分组口径

组进通过率内容
A 契约与导出✅ 22两个组件的导出形态、defaultProps、propTypes 缺失、keyExtractor 行为
B 白盒实例方法与错误路径✅ 9实例方法齐备、state.height 是 Animated.Value、非法 easing 抛错
C 回调与受控语义✅ 3回调记录格式、activeSections 类型
外部协议信息marker 夹逼量高度、动画中途采样、手风琴交互

六、实测一:折叠与展开高度逐像素精确

设备:Pura X View 模拟器,1320×2232(density 3)。

区域props折叠态间距展开态间距增量换算
AcollapsedHeight 默认 0,内容 120vp156516+360= 120 vp ✓ 精确等于内容高度
BcollapsedHeight=40,内容 90vp276426+150276−156 = 120 = 40vp ✓;426−156 = 270 = 90vp ✓
CrenderChildrenCollapsed=false,内容 120vp156515+359≈360(1px 取整差)

(A 区"间距 156"里包含就地按钮的高度,所以不是 0;这只影响绝对值,不影响增量。)

两条被这个测量排除的风险:

  1. measure() 的单位是对的。 库展开时走 _measureContent() → ref.measure(...),而折叠动画的目标值就来自它。如果 RNOH 的 measure() 返回物理像素而布局高度是 vp,展开高度会差 3 倍(1080px)。实测 360px = 120vp,排除。
  2. collapsedHeight 与内容高度是按同一单位比较的。 B 区折叠态 276 = 156 + 120(40vp)、展开态 426 = 156 + 270(90vp),两个数各自都精确落在 vp 网格上。

6.1 动画确实在动

展开 A 后立刻采样(受 devecocli ui layout 自身耗时影响,实际采样点晚于名义时间):

采样间距相对基线
基线1560
展开后不久385+331
动画结束516+360

+331 严格介于 0 与 360 之间 ⇒ 是动画过渡,不是瞬变 ✓

这也是"一次采样不够"的例子:只测最终值无法区分"动画"和"瞬间跳变",必须在动画过程中取一次中间值。

6.2 renderChildrenCollapsed

状态CONTENT-C 节点
折叠(默认 renderChildrenCollapsed=true)会出现(被裁掉但仍在树上)
折叠(renderChildrenCollapsed={false})不存在 ✓
展开出现 ✓
再折叠再次消失 ✓

⇒ 折叠时子节点被真正移除,不是单纯视觉隐藏。

在这里插入图片描述


七、实测二:两个隐蔽的行为边界

7.1 "挂载时就展开"其实不生效

C 区初始 collapsed={false}(意图是"默认展开")。实测结果出人意料:

C 区间距 = 156   ← 与 collapsedHeight=0 的折叠态完全相同
CONTENT-C 不存在

高度被锁在 0。 看源码就能解释:

const hasKnownHeight = !measuring && (measured || collapsed);
const style = { overflow: 'hidden', height: hasKnownHeight ? height : 0 };

挂载时 measured = false、collapsed = false ⇒ hasKnownHeight = false ⇒ height 恒为 0。

而 measured 只在 _measureContent() 里被置 true,_measureContent 又只由 _toggleCollapsed() 调用,_toggleCollapsed 只在 collapsed 发生变化时由 componentDidUpdate 触发 —— componentDidMount 里没有。

⇒ Collapsible 必须经历一次 collapsed 变化才会展开。

再结合 defaultProps.collapsed = true,可以看出这个库的设计前提是「从折叠状态开始」。实测验证:对 C 执行「折叠 → 展开」后,间距 156 → 515,CONTENT-C 出现 ✓

想做"默认展开"怎么办:不能只写 collapsed={false},得让它经历一次变化,例如挂载后切一次:

const [collapsed, setCollapsed] = useState(true);
useEffect(() => setCollapsed(false), []);   // 借一次变化触发测量

7.2 align 有一个隐藏前置条件

align 的实现是给内容层加 translateY,插值区间用的是 inputRange: [0, contentHeight]:

contentStyle.transform = [{ translateY: height.interpolate({
  inputRange: [0, contentHeight],
  outputRange: [contentHeight / -2, 0],   // center
})}];

⇒ contentHeight 为 0 时,插值退化成常量 0,align 完全没有效果。

实验一(没测过高度):三个实例(collapsedHeight=60、内容 120vp、align 分别 top/center/bottom)在"挂载即 collapsed=false"状态(即 7.1 的情况,contentHeight 从未被赋值)下折叠到 60vp:

实例内容相对 marker 的偏移(布局 dump)像素扫描到的文字位置
D1 top1871562
D2 center1871796
D3 bottom1872028

三者偏移完全相同,并且像素扫描与布局位置吻合(差 8–10px 是字形上沿)⇒ translateY 恒为 0,align 未生效 ✓

实验二(先真正展开一次):点「展开」(走 _measureContent,contentHeight 被赋值 120vp),再折叠到 60vp:

实例未变换偏移变换后偏移实测位移预期位移结论
D1 top18718700✓
D2 center18797−90px−30vp×3 = −90px✓ 精确
D3 bottom18754−133px−60vp×3 = −180px✗ 差 47px

center 精确吻合:translateY = (height − contentHeight)/2 = (60 − 120)/2 = −30vp = −90px ✓
bottom 按同一公式应为 height − contentHeight = −60vp = −180px,实测 −133px,差 47px 未能解释(反推该实例的 contentHeight 约为 104vp,而 center 那个是 120vp —— 为什么两个实例的 contentHeight 不同,我没有查到原因,如实记录为未解释项)。

实践结论:要用 align,必须先让组件经历过一次真正的展开;而且 bottom 的实际效果在本环境下与预期不符,不要依赖它。

在这里插入图片描述


八、实测三:Accordion 的受控语义

Accordion 是完全受控的:activeSections 由父组件持有,onChange 只负责给出新数组。

页面里放两个章节(keyExtractor 用默认实现,即返回 index),章节内容高度 90vp:

操作gapACC章节标题状态onChange 收到
初始296第一章(已折叠)/ 第二章(已折叠)—
点第一章566(+270 = 90vp ✓ 精确)第一章(已展开)、第二章(已折叠)onChange([0]) ✓
点第二章566(一收一开)第一章(已折叠)、第二章(已展开)onChange([1]) ✓ 替换而非累加
再点第二章296两章均折叠onChange([]) ✓

三条结论:

  1. 章节展开的高度精确等于内容高度(+270px = 90vp);
  2. expandMultiple 默认为 false:点第二个会替换第一个,而不是两个都展开([0] → [1]);
  3. onChange 给的是 key 而不是 index —— 因为默认 keyExtractor 恰好返回 index,两者在这个用例里数值相同;一旦你自定义了 keyExtractor,activeSections 里的就必须是你自己的 key。这一点很容易踩。
const [active, setActive] = useState<(string | number)[]>([]);

<Accordion
  sections={['第一章', '第二章']}
  activeSections={active}                       // 受控:必须自己保存
  renderHeader={(section, key, isActive) => <Text>{`${section}${isActive ? '(已展开)' : '(已折叠)'}`}</Text>}
  renderContent={(section, key) => <Text>{`第 ${key} 节正文`}</Text>}
  onChange={setActive}                          // 只给新数组
  duration={400}
/>

在这里插入图片描述


九、已知限制、交付包质量与一个类型问题

9.1 库本身的

  1. 挂载即 collapsed={false} 不会展开(高度锁 0),必须经历一次 collapsed 变化(见 7.1)。
  2. align 依赖 contentHeight,未测过高度时静默无效(见 7.2)。
  3. align='bottom' 实测位移与预期差 47px,未解释;align='center' 精确。
  4. Accordion 的事实必填属性 activeSections 没有 propTypes 声明,不传会 TypeError。
  5. 两个组件都没有 propTypes ⇒ 属性名写错没有开发期警告,只会静默失效。
  6. 非法 easing 会抛异常(这是好事,但要提前知道):
    Error: Invalid easing type "bogus"
    
    实测:直接调 _transitionToHeight 会同步抛出这条错误;合法的 'easeOutCubic' / 'easeInOutQuad' 等名字会被自动映射成 Easing.out(Easing.cubic) 这类组合。默认值 'easeOutCubic' 是安全的。
  7. Accordion 需要子路径导入(见 2.1)。
  8. enablePointerEvents 默认 false ⇒ 折叠状态下 pointerEvents='none',折叠区不接收触摸。如果你把可点元素放在折叠区内,展开前它是点不到的。

9.2 交付包质量

做得好的:

  • 实现零改动(7/8 文件与上游逐字符一致);
  • 零运行时依赖,不存在"依赖没被装上"的问题;
  • LICENSE 确为 MIT,与 package.json 一致;
  • spec.json 带 license 字段;
  • 契约测试换成了可运行的 node --test。

问题:

① README 没写 Accordion 要走子路径。 两个 README 的示例都是 import Library from 'react-native-collapsible';,既没提子路径,占位名 Library 也与真实导出名(Collapsible)不一致。照着写的人找不到 Accordion。

② README 的「能力一览」只有 2 行,Collapsible 的 9 个属性与 Accordion 的 18 个属性一个都没写。

③ spec.json 无 upstreamCommit(值可从 npm 查到),validation 是裸字符串 "pass",upstream.url 里还带着用户名。

④ 契约测试通过 ≠ 可用。 测试只断言包名、版本、主入口文件存在、中文 README 存在 —— 没有渲染组件、没有测折叠行为,也没测 .d.ts 能否通过类型检查(而后者恰好是坏的,见 9.3)。

⑤ 上游把 husky 的 git hooks 配置留在了发布包里。作为消费者安装时不会执行(没有 prepare/postinstall),所以无害,但发布包里留着它没有意义。

9.3 一个类型问题:.d.ts 与 React 19 不兼容

在本项目的类型环境下(@types/react@19.3.0、RN 0.84.1 自带类型、moduleResolution: bundler),写 <Collapsible /> 或 <Accordion /> 会报:

error TS2607: JSX element class does not support attributes because it does not have a 'props' property.
error TS2786: 'Collapsible' cannot be used as a JSX component.
  Its type 'typeof Collapsible' is not a valid JSX element type.
    Type 'Collapsible' is missing the following properties from type 'Component<any, any, any>':
    context, setState, forceUpdate, render, and 2 more.

我做了两组隔离测试来定位:

用例结果
本文件里自己声明的 class 组件(空类体 / 带 render / declare class)不报错 ✓
自己写的 .d.ts,声明形态与库完全一致(export default class … extends React.Component<P> {}),相对路径导入不报错 ✓
是否存在重复的 @types/react 副本不存在(全局只有一个 node_modules/@types)
import Collapsible from 'react-native-collapsible'报错 ✗
import Accordion from 'react-native-collapsible/Accordion'报错 ✗

⇒ 可以确定:这个库的 .d.ts 在本项目的类型环境下无法作为 JSX 组件使用,而且问题不在"空类体"这个写法本身,也不在"重复的 React 类型副本"。具体机制我没有定位到,如实记录。

影响与绕法:运行时完全不受影响(Babel 剥掉类型,Metro 打包 exit 0,设备上 34/34 全通过)。要在 TS 项目里带类型使用:

const SafeCollapsible = Collapsible as unknown as React.ComponentType<any>;

或对使用处加 @ts-expect-error。对工程质量敏感的项目,建议把这条写进接入清单。

9.4 本次验证的边界

  1. 只在一台模拟器上验证(Pura X View,density 3),没有真机。
  2. align='bottom' 那 47px 的偏差未定位原因。
  3. enablePointerEvents 未验证(折叠时触摸是否真的被吞掉)。
  4. expandFromBottom / renderAsFlatList / renderFooter / renderSectionTitle / touchableComponent / disabled / containerStyle / sectionContainerStyle 未逐一验证。
  5. style 属性透传、以及展开状态下改变 collapsedHeight 的行为未测。
  6. 长列表性能、多个折叠区同时动画的性能未测。
  7. 没有跨平台对照(未在 iOS/Android 上跑同一份用例)。

未改动库代码。


十、常见问题

Q1:需要 HAR、权限或 ohpm 接线吗?

都不需要。纯 JS 且零运行时依赖,接进宿主只改 package.json、metro.config.js、index.js 三处。判断信号是 link-harmony 计数不增加(本次 linked 10 libraries, skipped 2 libraries)。

Q2:怎么装?

npm install "git+https://atomgit.com/oh-react-native/react-native-collapsible.git#1.6.2-ohos-1.0.0"
# 或本地 file: 方式
npm install ../react-native-collapsible

Q3:Accordion 怎么导入?

import Accordion from 'react-native-collapsible/Accordion';   // 子路径,必须

不能写成 import {Accordion} from 'react-native-collapsible' —— 运行时是 undefined。

Q4:我想让它一开始就是展开的,怎么写?

不能只写 collapsed={false}(挂载时高度会被锁在 0)。要借一次 collapsed 变化来触发测量:

const [collapsed, setCollapsed] = useState(true);
useEffect(() => { setCollapsed(false); }, []);
<Collapsible collapsed={collapsed} duration={300}>…</Collapsible>

Q5:collapsedHeight 是干什么的?

折叠时保留的高度。默认 0(完全收起)。实测精确:collapsedHeight={40} 在 density 3 的设备上折叠态高度正好 120px = 40vp。

常见用法是"折叠起来只露出一行标题":

<Collapsible collapsed={c} collapsedHeight={40} duration={300}>
  <View style={{height: 200}}>…</View>
</Collapsible>

Q6:align 怎么用?为什么我看不出效果?

align 控制过渡过程中内容的对齐方向(top 默认 / center / bottom)。两个注意点:

  1. 内容必须比折叠后的高度高,位移才看得出来(translateY 是按 height / contentHeight 插值的);
  2. 必须先经历过一次真正的展开 —— contentHeight 为 0 时插值退化成常量 0,align 静默失效(见 7.2)。

实测:align='center' 的位移精确等于 −(contentHeight − height)/2;align='bottom' 在本次环境下的实测值与公式不符(差 47px),不建议依赖。

Q7:easing 传字符串还是函数?

都行。字符串走库内置的名称映射(支持 linear、easeInOut*、easeOut*、easeIn* 系列,会自动组合成 Easing.out(Easing.cubic) 这类形式)。传了不认识的名字会抛 Invalid easing type "xxx"。

Q8:Accordion 的 activeSections 里放 index 还是 key?

放 keyExtractor 返回的值。默认 keyExtractor 返回 index,所以看起来像 index;自定义之后就必须放自定义的 key。onChange 给你的也是这组 key。

Q9:折叠状态下里面的按钮点不动?

enablePointerEvents 默认为 false,折叠时外层会被设为 pointerEvents='none'。要让它可点:

<Collapsible collapsed={c} enablePointerEvents>…</Collapsible>

Q10:TypeScript 里 <Collapsible /> 报 TS2786 怎么办?

这是该包 .d.ts 与本项目 React 19 类型不兼容导致的(运行时无影响)。绕法见 9.3。


小结

react-native-collapsible 是个"小但扎实"的库:两个组件、纯 JS、零运行时依赖,接进宿主只要三处改动。

这轮值得留下来的经验是:

  1. 动画目标是"布局属性"时,优先用布局 dump 而不是像素比对。 这个库动画的是高度,Y(markerB) − Y(markerA) 的差值就是精确的高度增量 —— 比截图比对便宜得多,而且精确到像素(实测 360 / 270 / 120 全部落在 vp 网格上)。

  2. measure() 的单位必须实测。 库展开时用 ref.measure() 取内容高度作为动画目标。如果 RNOH 的 measure() 返回物理像素而布局单位是 vp,展开高度会差 3 倍。本轮实测 360px(正确),但这个风险不测就不知道。

  3. 要验证"初始状态",不要只验证"切换后的状态"。 "挂载时 collapsed={false}"这个用例专门为它设计,一测就露出来:高度被锁在 0,因为 hasKnownHeight = !measuring && (measured || collapsed) 在挂载时为 false。"默认展开"是这个库的反直觉点。

  4. prop 可能有隐藏前置条件。 align 依赖 contentHeight,而 contentHeight 只有测过一次高度才有值 ⇒ 未测过高度时 align 静默无效。验证一个 prop,要先确认它依赖的状态是否就绪。

  5. “测量不到"不等于"没有效果”。 我一度以为布局 dump 不反映 transform,后来用"先展开再折叠"造出真实位移,发现 dump 是反映的。在把"测不到"写成结论之前,先造一个确定有差异的对照组。

  6. 包可能有多个入口。 main 只导出 Collapsible,Accordion 在子路径 —— 写错导入时只有在类型检查阶段才会报错(TS2614),Metro 不报,直到运行时渲染才炸。


本篇用到的库

项内容
三方库react-native-collapsible(上游 1.6.2 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-collapsible
适配 TAG1.6.2-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS)
运行时依赖无(只有 react / react-native 两个 peerDependency)
公开入口两个:react-native-collapsible(Collapsible)与 react-native-collapsible/Accordion(Accordion)
上游仓库https://github.com/oblador/react-native-collapsible(基线 commit c79b7c01ccb80968e9c01d61ec9bf89f2d20d4c0,MIT)
宿主工程RNOH084Demo(测试页 rnAppKey = CollapsibleTestApp)

接入方式(纯 JS,零原生接线):

// package.json
"react-native-collapsible": "file:../react-native-collapsible"
// 或直接从适配仓库装(替代上面的 file: 方式)
"react-native-collapsible": "git+https://atomgit.com/oh-react-native/react-native-collapsible.git#1.6.2-ohos-1.0.0"
// metro.config.js —— file: 装进来是 junction,必须让 Metro 找得到源码
watchFolders: [path.resolve(__dirname, '../react-native-collapsible')],
import Collapsible from 'react-native-collapsible';
import Accordion from 'react-native-collapsible/Accordion';   // ← 子路径,必须

// 折叠容器:注意三个尺寸相关 props 要一起考虑
<Collapsible
  collapsed={collapsed}
  collapsedHeight={0}          // 折叠时保留的高度(vp)
  duration={300}
  easing="easeOutCubic"
  align="top"                  // center 已实测精确;bottom 实测与预期不符
  enablePointerEvents={false}  // false 时折叠区不接收触摸
  renderChildrenCollapsed      // false 时折叠即移除子节点
  onAnimationEnd={() => {}}
>
  <View style={{height: 120}}><Text>内容</Text></View>
</Collapsible>

// 手风琴:完全受控
const [active, setActive] = useState<(string | number)[]>([]);
<Accordion
  sections={['第一章', '第二章']}
  activeSections={active}      // 事实必填
  onChange={setActive}         // 只给新数组,必须自己 setState
  renderHeader={(section, key, isActive) => <Text>{`${section}${isActive ? '(展开)' : '(折叠)'}`}</Text>}
  renderContent={(section, key) => <Text>{`第 ${key} 节`}</Text>}
  duration={400}
/>
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey CollapsibleTestApp

# 外部测量:marker 夹逼量折叠区高度
devecocli ui layout | Select-String 'MARK-A'

验证环境

项版本
React Native0.84.1
React19.2.3(@types/react 19.3.0)
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
宿主 HAP 产物entry-default-signed.hap(81.77 MB)
本次增量构建assembleHap 4 分 22 秒,HAP +49,155 字节
验证规模设备侧 34 / 34 断言全部通过;折叠/展开高度外部实测 3 个区域 + 手风琴 4 步(156→516 / 276→426 / 296→566→566→296);动画中途采样 1 次;renderChildrenCollapsed 3 个状态;align 两种前置条件下共 6 组读数

欢迎加入 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐