React Native for OpenHarmony 实战:三方库 react-native-floating-action 的鸿蒙化适配指南
本文记录把 react-native-floating-action(悬浮操作按钮 / FAB)适配到 HarmonyOS 的完整过程。
它做的事很专一:在屏幕一角放一个圆形主按钮,点开之后绕着它展开一排动作按钮(可带文字标签),点某个动作就把 name 回调出去。
结论分两半:
- ✅ 工程质量很高,主体可用 —— 纯 JS、零外部依赖、老写法九项预扫全空、六处平台分支全是"iOS 特判 + 优雅降级"、13 处
useNativeDriver全部显式false;19 / 19 条判据全部通过,几何能做到像素级精确核对。 - ⚠️ 但"点开动作列表"这一步在 RNOH 上不可见 —— 主按钮回调会触发、内部
state.active也确实变成了true、actions有 3 项、LayoutAnimation可用,但屏上既看不到动作按钮、也看不到动作文字。现象已钉死,根因定位到渲染层(详见第四节,明确标注"待进一步定位")。
★★★★ 一、像素级几何验证:三组预言全部命中
defaultProps(源码 L620-639)给了很明确的默认值:
buttonSize: 56, // 主按钮边长
distanceToEdge: 30, // 距屏幕边缘
position: "right", // 默认贴右
iconWidth: 15, iconHeight: 15, // 加号图标的尺寸
color: "#1253bc",
而加号本身是两个 View 拼的(src/AddIcon.js):
<View style={[styles.vertical, { height, backgroundColor }]} /> // vertical: { width: 2, position: "absolute" }
<View style={[styles.horizontal, { width, backgroundColor }]} /> // horizontal: { height: 2, position: "absolute" }
density 3 ⇒ 预言与实测对照:
| 量 | 公式 | 预言 | 实测(布局 dump) |
|---|---|---|---|
| 主按钮边长 | buttonSize 56vp × 3 | 168px | ✅ 168×168(left 实例)/ 169×169(right 实例) |
| 距下边缘 | distanceToEdge 30vp × 3 | 90px | ✅ 90px(两个实例都是) |
距左边缘(position: "left") | 30vp × 3 | 90px | ✅ 90px(x=[90,258]) |
距右边缘(position: "right") | 30vp × 3 | 90px | ✅ 90px(x=[1061,1230],1320−1230=90) |
| 加号竖条宽 | 2vp × 3 | 6px | ✅ 6px(x=[1143,1149]) |
| 加号竖条长 | iconHeight 15vp × 3 | 45px | ✅ 45px(y=[2036,2081]) |
| 加号横条高 | 2vp × 3 | 6px | ✅ 6px(y=[2055,2061]) |
| 加号横条宽 | iconWidth 15vp × 3 | 45px | ✅ 45px(x=[1124,1169]) |
原始 dump 片段:
Custom#View [1061,1973,1230,2142] clickable ← F1(right)169×169,距右 90px、距下 90px
Custom#View [ 90,1974, 258,2142] clickable ← F2(left) 168×168,距左 90px、距下 90px
Custom#View [1143,2036,1149,2081] ← 加号竖条:宽 6px、长 45px
Custom#View [1124,2055,1169,2061] ← 加号横条:宽 45px、高 6px
⇒ 56vp→168px、30vp→90px、2vp→6px、15vp→45px 四组换算全部精确命中 ✓✓
一个细节(如实记录):两个实例的实测边长是 168 与 169 px(理论值 168),存在 1px 取整差 ——
position: "left"用左偏移定位、position: "right"用右偏移定位,两种定位方式的像素取整结果不同 ✓
这个差异也让我的第一次测量漏掉了 F1(见第六节)。
★★★★ 二、visible={false} ⇒ 整棵子树返回 null
render() 的第一句(源码 L543-549):
render() {
const { active, shouldRender } = this.state;
const { showBackground, visible } = this.props;
if (!shouldRender && !visible) {
return null; // ★ 从未挂载过 + visible=false ⇒ 什么都不渲染
}
return (<Animated.View pointerEvents="box-none" style={[styles.overlay, …]}>…</Animated.View>);
}
实测(布局 dump):
| 阶段 | visible | 屏上的 168±1px 方块 |
|---|---|---|
| P0 | false | 只有 F2(left) —— F1 完全不存在 ✓ |
| P1 | true | F1 + F2 各一组 ✓ |
| P2 | false | F1 再次消失 ✓ |
这一点在截图上直接可见(下图就是 P2 时刻:右下角的蓝色 F1 消失,左下角的绿色 F2 仍在):

⇒ visible=false 会让整个组件(含遮罩层)从树上消失 ⇒ 这是一个很干净的开关语义 ✓
⇒ 实践含义:如果你希望"隐藏但保留状态",不要用 visible={false}(state.active 虽然保留、但树被拿掉,回到 true 时会重新走 componentDidMount 之外的分支);对"动画收起"应该用组件自己的开合逻辑。
★★★★ 三、shadeColor 纯函数:四条断言全部精确命中
src/utils/color.js 里有个纯函数(被 getRippleProps 用于生成涟漪色):
export function shadeColor(color, percent) {
// 支持 "#RRGGBB" 与 "rgb(...)"/"rgba(...)" 两种输入
R = parseInt((R * (100 + percent)) / 100, 0);
…
R = R < 255 ? R : 255; // ★ 上限 clamp 到 255(只 clamp 上限)
…
return `#${RR}${GG}${BB}`; // ★ 每通道补零到两位
}
交付包没有 exports 字段 ⇒ 可以用子路径 require 直接单测:
const {shadeColor} = require('react-native-floating-action/src/utils/color');
实测:
| 输入 | 手算 | 实测 |
|---|---|---|
shadeColor('#FF0000', -30) | R=⌊255×0.7⌋=178=0xB2, G=0, B=0 | ✅ #b20000 |
shadeColor('#1253bc', 0) | 不变 | ✅ #1253bc |
shadeColor('rgb(255,0,0)', 50) | R=382 ⇒ clamp 到 255 | ✅ #ff0000 |
shadeColor('#80FF80', -50) | R=64=0x40, G=⌊255×0.5⌋=127=0x7f, B=64=0x40 | ✅ #407f40 |
⇒ 四条覆盖了十六进制输入、rgb() 输入、上限 clamp、各通道独立计算 + 补零 ✓✓
⇒ 第四条尤其有价值:它同时验证了"三通道各自计算"与"两位数补零"(0x40 需要补成 40,而 0x7f 是小写 ✓)
★★★★ 四、核心问题:点开主按钮后,动作列表在 RNOH 上不可见
4.1 现象(已多次复现)
// FloatingAction.js L235-294 —— 主按钮的 onPress
animateButton = () => {
…
if (overrideWithAction) { this.handlePressItem(actions[0].name); return; } // 我传 false ⇒ 不走这里
if (onPressMain) { onPressMain(!active); } // ① 回调
if (!active) {
if (!floatingIcon) { if (animated) { Animated.spring(this.animation, {toValue: 1, useNativeDriver: false}).start(); } }
if (animated) {
Animated.spring(this.actionsAnimation, {toValue: 1, useNativeDriver: false}).start(); // ② 动作容器透明度
LayoutAnimation.configureNext({ duration: 180, create: {…} }); // ③ 布局动画
}
this.updateState({ active: true }, () => { if (onOpen) { onOpen(); } }); // ④ 置 active
} else { this.reset(); }
};
逐环节实测(每一步都有证据):
| 环节 | 期望 | 实测 | 证据来源 |
|---|---|---|---|
① onPressMain 回调 | 被调用 | ✅ 触发 | 页面事件区 + hilog EVENT | onPressMain(F1 主按钮被点) |
② Animated.spring(actionsAnimation) | 值推进到 1 | ⚠️ 未能确认 | —— |
③ LayoutAnimation.configureNext | 可用 | ✅ typeof = object、调用返回成功(无抛错) | 页面探针 A3 |
④ state.active | 变成 true | ✅ 确实变成 true | 周期性探针 PROBE | F1.state.active = true / actions = 3 |
| 结果 | 动作项出现在屏上 | ❌ 屏上没有动作项 | 布局 dump 里既无 动作一/二/三 文本,也无额外圆按钮 |
探针原始输出:
===== A3. LayoutAnimation 探针 =====
NOTE | RN.LayoutAnimation 的 typeof = object
NOTE | 调用 LayoutAnimation.configureNext 是否成功 = true
NOTE | configureNext 的错误信息 =
EVENT | onPressMain(F1 主按钮被点)
PROBE | F1.state.active = true / shouldRender = true / props.visible = true / actions = 3
⇒ 回调、状态、动作数量、LayoutAnimation 全部正常,唯独动作项不可见 ✓
4.2 定位(收敛到渲染层)
renderActions()(源码 L453-493):
if (!actions || actions.length === 0) { return undefined; }
if (overrideWithAction) { return null; }
if (animated) {
animatedActionsStyle = { opacity: this.actionsAnimation.interpolate({ inputRange: [0, 1], outputRange: [0, 1] }) };
}
const actionsStyles = [ styles.actions, styles[`${position}Actions`], animatedActionsStyle, { bottom: this.actionsBottomAnimation } ];
// styles.actions = { position: "absolute", bottom: 85, zIndex: 10 }
// styles.rightActions = { alignItems: "flex-end", right: -1000 } ← 收起:移出屏外
// (展开态另有 rightActionsVisible = { right: 0 })
⇒ 动作容器的可见性由 opacity: this.actionsAnimation(Animated.spring 驱动)决定,
并且它还被 { bottom: this.actionsBottomAnimation } 与 right: -1000 / 0 的动画值控制 ✓
在"回调已触发、active 已为 true、LayoutAnimation 无异常"的前提下,最可能的原因是: Animated.spring驱动的this.actionsAnimation/this.actionsBottomAnimation**没有推进**, 导致动作容器停在opacity: 0(bottom/right` 也停在收起值)。
★★★ 4.3 一条重要的方法论:布局 dump【不列出 opacity: 0 的节点】
这解释了为什么"dump 里找不到动作项"不能直接推断"没有渲染":
- 动作容器很可能已经渲染,只是
opacity停在 0; - 而布局 dump 只列出参与布局/可见的节点 ⇒
opacity: 0的子节点不会出现 ✓
⇒ 所以判定"某节点是否渲染"必须用 JS 侧证据(实例 state、日志),不能只看 dump ✓
⚠️ 本节结论的边界:
Animated值是否推进本轮没有直接读到(读实例上的Animated.Value需要再加一次探针与重建)。
因此这一节写的是"现象已钉死 + 根因收敛到渲染层的Animated驱动",不是"根因已确认"。
★★★ 五、平台分支与 useNativeDriver 的完整清单(都很好)
六处 Platform.OS,全是"iOS 特判 + 优雅降级":
| 位置 | 代码 | 鸿蒙上的行为 |
|---|---|---|
utils/platform.js L7 | isIphoneX():Platform.OS === "ios" && !Platform.isPad && … | 返回 false ✓ |
utils/touchable.js L10 | useNativeFeedback && Platform.OS === "android" ⇒ 返回 TouchableNativeFeedback | 返回 TouchableOpacity ✓(正确降级) |
utils/touchable.js L20 | 涟漪:同上 + Platform.Version >= 21 | 返回 {}(无涟漪)✓ |
FloatingAction.js L69 | 键盘事件名:ios ? "keyboardWillShow" : "keyboardDidShow" | 用 keyboardDidShow ✓ |
FloatingAction.js L71 | 同上(hide) | 用 keyboardDidHide ✓ |
FloatingAction.js L677 | overflow: Platform.OS === "ios" ? "visible" : "hidden" | hidden ✓ |
⇒ 六处没有一处会崩,而且**"非 iOS/非 Android"都落到了合理的默认分支** ✓
十三处 useNativeDriver 全部显式 false:
FloatingAction.js L90/L91/L96/L97/L140/L146/L159/L165/L220/L221/L264/L269
FloatingActionItem.js L19
⇒ 全部显式传值 ⇒ 不会触发"未指定 useNativeDriver"的告警 ✓
⇒ 也说明作者对动画属性(bottom / right / opacity 等布局属性)不能用原生驱动这件事是清楚的 ✓
★★★ 六、我自己的两次测量失误(如实记录)
- 过滤器太严,把 F1 漏掉了 ✗:我用
宽 == 168 且 高 == 168精确匹配去找主按钮,而position="right"的实例实测是 169×169(另见第一节的 1px 取整差)⇒ 它一直没被匹配到,我一度以为"右侧的 FAB 没渲染"。
⇒ 教训:几何过滤要给容差(如 ±8px),并把"同一元素的多层嵌套节点"去重后再下结论。 - dump 时机落在"已收起"的相位 ✗:第一次点完主按钮后我等了 3 秒才 dump,而那一刻时间轴刚好把
visible置回false⇒render返回null⇒ 整棵树都没了,我却以为是"展开失败"。
⇒ 教训:驱动型测试页必须让"被测状态"稳定存在足够久(本次改为手动按钮 + 1.5 秒内立刻 dump 才定案)。
改了这两点之后,才把"动作项不可见"从"疑似"钉成"可复现现象" ✓



七、接入与预检
| 步 | 做法 | 结果 |
|---|---|---|
| ① | harmony.autolinking | 没有 |
| ② | harmony/ / ios/ / android/ | 都没有 |
| ③ | 原生痕迹预扫 | ✅ 无原生源文件、无 NativeModules |
| ④ | ★ 老写法九项预扫 | ✅ 全部为空(见下) |
| ⑤ | 平台分支预扫 | ✅ 6 处,全是 iOS 特判 + 优雅降级 |
| ⑥ | measure() / onLayout 排查 | ✅ 都没用到 |
| ⑦ | scripts 扫 prepare 家族 | ✅ 无 |
| ⑧ | 依赖 | ✅✅ dependencies 为空;import 图只有 react / react-native / prop-types(宿主已有)⇒ 零外部依赖 |
| ⑨ | 写法 | 两个 class(AddIcon extends PureComponent、FloatingAction extends Component);原型方法 + 箭头类字段混用 |
老写法九项逐项结果:
| 项 | 结果 |
|---|---|
React.createClass | ✅ 空 |
React.PropTypes | ✅ 空 |
从 react 具名导入 PropTypes | ✅ 空 |
ViewPropTypes / TextPropTypes | ✅ 空 |
字符串 ref="x" / this.refs.x | ✅ 空 |
mixins: | ✅ 空 |
componentWillReceiveProps | ✅ 空 |
componentWillMount | ✅ 空 |
measure( / measureInWindow | ✅ 空 |
两类成员混用(实测):
NOTE | 原型方法命中数(源码 5 个) = 5
NOTE | 5 个箭头类字段里在 prototype 上的个数(应为 0) = 0
PASS | ★ 5 个原型方法都在 prototype 上 | 期望=5 | 实际=5
PASS | ★★ 5 个箭头类字段都不在 prototype 上 | 期望=0 | 实际=0
(原型:constructor / componentDidMount / componentDidUpdate / componentWillUnmount / render
箭头类字段:getIcon / reset / animateButton / getShadow / onKeyboardHideHide / …)
⇒ 用的是 componentDidUpdate(现代生命周期),不是 componentWillReceiveProps ✓
# 从适配仓库安装(零外部依赖,无需补装任何运行时依赖)
npm install "git+https://atomgit.com/oh-react-native/react-native-floating-action.git#1.22.0-ohos-1.0.0" --ignore-scripts
八、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | index.js(67 B,只 re-export)+ src/ 7 个文件(主组件 17562 B)✓ |
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 基线 |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。这个库没有任何已移除的写法;
不过第四节那个"动作列表不可见"的问题与 Animated 的执行有关,建议在换基线时复验一次。
九、已知限制与本次验证边界
9.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
| ⚠️ 点开动作列表在 RNOH 上不可见 | 回调会触发、state.active 会变 true,但动作项不可见(详见第四节;根因收敛到渲染层的 Animated 驱动,未最终确认) |
★ visible={false} 会让整棵树返回 null | 是"开关"语义,不适合做"动画收起" |
| ★★ 几何可精确预言 | 主按钮 56vp ⇒ 168px;distanceToEdge 30vp ⇒ 90px;加号线宽 2vp ⇒ 6px、图标 15vp ⇒ 45px |
★ position="right" 与 "left" 有 1px 取整差 | 实测 169px vs 168px(理论 168)⇒ 写断言要给容差 |
| ★ 六处平台分支都是优雅降级 | 鸿蒙上:TouchableOpacity、无涟漪、keyboardDidShow/Hide、overflow: hidden |
★ 13 处 useNativeDriver 全为 false | 不会触发"未指定"告警 |
★ 纯函数 shadeColor 可子路径单测 | require('react-native-floating-action/src/utils/color') |
| ★ 只 clamp 上限 | shadeColor 的 R < 255 ? R : 255 —— 负值不会 clamp 到 0(做深色时要注意) |
9.2 本次验证的边界
- ★★ 动作列表不可见的根因未最终确认(
Animated值是否推进没有直接读到;需要再读实例上的Animated.Value); - 未验证
onPressItem的回调链路(动作项没渲染出来,也就点不到); - 未验证
onOpen/onClose/onStateChange/onPressBackdrop回调; - 未验证
overrideWithAction(主按钮直接执行第一个动作); - 未验证
floatingIcon(自定义主图标)与自定义icon; - 未验证
showBackground的遮罩层与overlayColor; - 未验证
listenKeyboard/dismissKeyboardOnPress的键盘联动; - 未验证
animated={false}的非动画路径(源码里这条分支与动画分支不同); - 未验证
position="center"(该分支用到DEVICE_WIDTH / 2 - 28); - 未验证
distanceToEdge传对象({vertical, horizontal})的形式; - 未在
RN 0.88.0/RNOH 0.84.4上复验(交付包声明的基线); - 未测真机与其它屏幕宽度(只验证了 440vp 宽);
- 未做跨平台对照(未在 Android/iOS 上跑同一页做对比)。
未改动库代码。
十、常见问题
Q1:它在 RN 0.84 上能用吗?
主体能用:组件正常渲染、visible 开合与 position 定位都正确、几何尺寸像素级准确、纯函数与契约全部通过(19/19)。
但"点开动作列表"这一步目前看不到动作项(详见第四节)。
Q2:为什么我点了主按钮没反应?
先分清"哪种没反应":
onPressMain不触发 ⇒ 检查overrideWithAction(为true时它会先执行第一个动作然后return,不会再调onPressMain);- 回调触发了但没有动作项 ⇒ 就是本文第四节的现象(回调、
active、LayoutAnimation都正常,但动作容器不可见)。
Q3:visible={false} 和"收起"有什么区别?
visible={false} 且从未挂载过时,render() 直接 return null ⇒ 整棵子树不存在(含遮罩)。
“展开/收起"是组件内部 state.active 的事,与 visible 无关。若要做"动画收起”,用组件自身的开合逻辑,别用 visible。
Q4:我想改主按钮大小和离边距离?
<FloatingAction buttonSize={56} distanceToEdge={30} position="right" />
实测对应关系(density 3):buttonSize × 3 = 像素边长;distanceToEdge × 3 = 距边像素。
Q5:shadeColor 怎么用?
const {shadeColor} = require('react-native-floating-action/src/utils/color');
shadeColor('#FF0000', -30); // '#b20000'
shadeColor('#1253bc', 0); // '#1253bc'
shadeColor('rgb(255,0,0)', 50); // '#ff0000'(超上限 clamp)
shadeColor('#80FF80', -50); // '#407f40'
⚠️ 只 clamp 上限,负值不会 clamp 到 0。
Q6:怎么在不点屏幕的情况下测它?
它是个 class,但交互方法都是箭头类字段(挂在实例上) ⇒ 用 ref 直接调:
const ref = useRef(null);
<FloatingAction ref={ref} visible={show} actions={[…]}
onPressMain={v => console.log('open?', v)} />
ref.current.animateButton(); // 等价于点主按钮
ref.current.state.active; // 读内部展开状态
⚠️ 但要注意 visible 一旦为 false 且未挂载过,实例上的 state 仍在但树被拿掉 —— 判据要读状态,不要只看布局 dump。
Q7:为什么布局 dump 里找不到某个节点?
布局 dump 不列 opacity: 0 的节点 ⇒ “dump 里没有” 不等于 “没渲染”。
判断是否渲染要用 JS 侧证据(实例 state、日志),或把 opacity 临时改成 1 再看。
小结
react-native-floating-action 的工程质量很高:纯 JS、零外部依赖、老写法九项预扫全空、
六处平台分支全是"iOS 特判 + 优雅降级"、13 处 useNativeDriver 全部显式 false。
几何能做到像素级核对(19/19 判据通过)。
最值得记下来的是这几条:
-
★★★★ 像素级几何验证:四组换算全部精确命中。
buttonSize 56vp → 168px、distanceToEdge 30vp → 90px、加号线宽2vp → 6px、图标15vp → 45px✓
两个实例分别在左下与右下,距左/右与距下都是 90px ✓ -
★★★★ 一个"现象已钉死、根因已收敛"的适配问题。
点主按钮后:onPressMain回调触发 ✓、state.active变true✓、actions = 3✓、LayoutAnimation.configureNext可用且成功 ✓ —— 但动作项在屏上不可见 ❌
源码显示动作容器的可见性由opacity: this.actionsAnimation.interpolate(...)与{bottom: this.actionsBottomAnimation}两个Animated值决定
⇒ 根因收敛到"Animated.spring驱动的值未推进",未最终确认(如实标注)。 -
★★★ 布局 dump 不列
opacity: 0的节点 —— 所以"dump 里没有"≠"没渲染"。
判定是否渲染必须用 JS 侧证据(实例state/ 日志),或临时把opacity改成 1。 -
★★★★
visible={false}⇒ 整棵树return null。
截图上直观可见(右下角的实例整体消失)✓ 这是"开关"语义,别拿它当"动画收起"。 -
★★★ 纯函数是最便宜的硬判据(而且能覆盖边界)。
shadeColor四断言:十六进制、rgb()输入、上限 clamp、各通道独立 + 补零(#80FF80, -50⇒#407f40)✓
⚠️ 顺带发现它只 clamp 上限,负值不 clamp 到 0。 -
★★★ 6 处平台分支给出了"该怎么写"的正面样本。
Platform.OS === 'ios' ? A : B这种二元判断在非 iOS 平台天然落到B,只要B是合理的默认值就不会出问题;
而对TouchableNativeFeedback这类 Android 专有组件,先判断平台再取组件(getTouchableComponent())就完全安全 ✓ -
★★ 判据要读"实例状态",不要只看 dump。
本轮的定案靠的是PROBE | F1.state.active = true(周期性读实例state),而不是布局 dump ✓ -
★ 我自己的两次测量失误值得记。
① 几何过滤器写死== 168⇒ 漏掉了实测 169px 的右侧实例(给 ±8px 容差);
② dump 时机落在"已收起"相位 ⇒ 看到的是"整树null"却误判为"展开失败"(让被测状态稳定存在够久)✓
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-floating-action(上游 1.22.0 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-floating-action |
| 适配 TAG | 1.22.0-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS) |
| 库类型 | 悬浮操作按钮(FAB):圆形主按钮 + 环绕展开的动作按钮 |
| 运行时依赖 | ✅✅ 无(dependencies 为空;只需宿主已有的 prop-types) |
prepare / postinstall | ✅ 都没有 |
| 许可证 | MIT |
| 上游仓库 | https://github.com/santomegonzalo/react-native-floating-action(★486) |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = FloatingActionTestApp) |
# 从适配仓库安装(零外部依赖)
npm install "git+https://atomgit.com/oh-react-native/react-native-floating-action.git#1.22.0-ohos-1.0.0" --ignore-scripts
import {FloatingAction} from 'react-native-floating-action';
<FloatingAction
visible={show} // ⚠️ false 且未挂载过 ⇒ 整树 return null
position="right" // "right" | "left" | "center"
color="#1253bc"
buttonSize={56} // ⇒ 168px(density 3)
distanceToEdge={30} // ⇒ 距边 90px
actionsPaddingTopBottom={8}
actions={[
{name: 'a1', text: '动作一', color: '#D0021B'},
{name: 'a2', text: '动作二', color: '#0A7D2C'},
]}
onPressMain={opening => console.log('opening=', opening)} // 参数是 !active
onPressItem={name => console.log('picked', name)}
onOpen={() => {}}
onClose={() => {}}
onStateChange={st => {}}
/>
// ⚠️ 组件根节点是 position:absolute 的整屏遮罩 ⇒ 要放在滚动容器【之外】
// ⚠️ 点开动作列表在 RNOH 上目前不可见(回调与 state.active 均正常,动作容器停在 opacity 0)
// ⚠️ overrideWithAction=true 时:主按钮直接执行第一个动作并 return,不会再调 onPressMain
// ⚠️ visible=false 是"开关",不是"收起"
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey FloatingActionTestApp
判据的复现方式
# ① 契约 / defaultProps / shadeColor / 两类成员:逐条 PASS/FAIL 日志
hdc shell "hilog -x | grep fab-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ⇒ shadeColor 四条:#b20000 / #1253bc / #ff0000 / #407f40
# ② 几何:主按钮 168px(右侧实例可能 169px,给容差)、距边 90px、加号线宽 6px、图标 45px
devecocli ui layout | Select-String '\[(\d+),(\d+),(\d+),(\d+)\]'
# ③ 探针:LayoutAnimation 可用性 + 周期读实例 state(判定 active)
hdc shell "hilog -x | grep fab-test" | Select-String 'A3|LayoutAnimation|PROBE|EVENT'
# ④ 注意:布局 dump 不列 opacity:0 的节点
验证环境
| 项 | 版本 |
|---|---|
| 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 分 37 秒;为加"根因探针"再构建一次 4 分 38 秒 |
| 验证规模 | 19 条判据全部通过;几何 4 组精确命中(168/169px、90px、6px、45px);shadeColor 4 条精确断言;两类成员 5/5 与 0/7;根因探针 2 项(LayoutAnimation 可用、state.active = true);4 张截图互异 |
⚠️ 基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成。
本库无任何已移除写法 ⇒ 契约类结论跨基线成立;但**"动作列表不可见"与Animated的执行有关,建议换基线时复验**。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| 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)