查看: 187|回复: 0

鸿蒙RNOH LayoutAnimation不生效兼容配置与避坑

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在 React Native 中,LayoutAnimation 的价值是:只要在 setState 之前调用 configureNext,下一次布局变化就会自动带过渡,不需要创建 Animated.Value,也不需要手动控制动画开始和结束。折叠面板展开收起、列表项增减、表单展开更多选项,都属于它最合适的场景。原文作者在 React Native 0.84 + RNOH 0.84.1、HarmonyOS 6.0 上实测,本文围绕这些事实整理鸿蒙适配要点。

基础用法与生效前提
  1. import { LayoutAnimation } from 'react-native';
  2. const handleToggle = () => {
  3.   LayoutAnimation.configureNext(
  4.     LayoutAnimation.Presets.spring
  5.   );
  6.   setExpanded(!expanded);
  7. };
复制代码
布局变化必须是 RN Layout 系统能感知到的,例如条件渲染导致的显示隐藏、宽高变化、位置变化。configureNext 只对紧接着的一次布局刷新生效,所以必须在 setState 之前调用。它不会“记住”配置,每次新的布局变化都要重新调用一次。

Presets 与 create 自定义
内置 Presets 覆盖常见场景:
easeInEaseOut:300ms,透明度过渡,先慢后快再慢,适合面板展开收起、列表项增减等通用场景。
linear:500ms,透明度过渡,匀速,适合进度条、加载指示器等需要恒定速度的场景。
spring:700ms,透明度加弹性效果,通过 springDamping 控制弹性阻尼,适合弹窗出现、卡片飞入等需要活力感的场景。原文作者最常用 easeInEaseOut,spring 的 700ms 有时偏长。
  1. LayoutAnimation.configureNext(
  2.   LayoutAnimation.create(
  3.     500,
  4.     LayoutAnimation.Types.spring,
  5.     LayoutAnimation.Properties.scaleXY
  6.   )
  7. );
复制代码
原文示例涉及的动画类型包括 Types.spring、Types.easeInEaseOut、Types.linear、Types.keyboard,动画属性包括 Properties.scaleXY、Properties.opacity。经验取舍是:增删元素可用 scaleXY 配合 spring,位置变化用 easeInEaseOut 更平滑。

完整配置:create、update、delete 分离
  1. LayoutAnimation.configureNext({
  2.   duration: 500,
  3.   create: {
  4.     type: LayoutAnimation.Types.spring,
  5.     property: LayoutAnimation.Properties.scaleXY,
  6.     springDamping: 0.6,
  7.   },
  8.   update: {
  9.     type: LayoutAnimation.Types.easeInEaseOut,
  10.     springDamping: 0.6,
  11.   },
  12.   delete: {
  13.     type: LayoutAnimation.Types.linear,
  14.     property: LayoutAnimation.Properties.opacity,
  15.   },
  16. });
复制代码
这样可做到元素出现时弹性弹入,位置变化时平滑移动,元素消失时渐隐。原文在“商品列表动态筛选”里用过:筛选条件变化后,不符合条件的商品逐渐消失,新商品弹入,位置变化的商品平滑移动。

正确顺序与常见误用
  1. // 错误写法:先 setState 再 configureNext,动画赶不上
  2. setVisible(!visible);
  3. LayoutAnimation.configureNext(Presets.spring);
复制代码
  1. // 正确写法:先配置动画,再更新状态
  2. LayoutAnimation.configureNext(Presets.spring);
  3. setVisible(!visible);
复制代码
另一个隐蔽问题是连续调用两次 configureNext,只有最后一次生效。而且 configureNext 只对紧接着的一次布局变化生效。原文在列表删除场景中只调用一次 configureNext,结果只有第一个 item 的移除有动画,后面移除都直接跳变;改成每次操作前都调用一次后恢复正常。

列表增删、Accordion 与表单展开
列表增删时,在 addItem 和 removeItem 触发 setState 前分别调用 configureNext,就能让新增和删除自动淡入淡出,代码量比 Animated 少很多。但如果列表超过 20 项,LayoutAnimation 可能因为所有 item 布局变化都被动画化而性能下降,此时建议用 Animated 只控制真正变化的部分。
  1. const addItem = () => {
  2.   LayoutAnimation.configureNext(
  3.     LayoutAnimation.create(
  4.       300,
  5.       LayoutAnimation.Types.easeInEaseOut,
  6.       LayoutAnimation.Properties.opacity
  7.     )
  8.   );
  9.   setItems(prev => [
  10.     ...prev,
  11.     { id: Date.now(), text: `待办事项 ${prev.length + 1}` },
  12.   ]);
  13. };
  14. const removeItem = (id: number) => {
  15.   LayoutAnimation.configureNext(
  16.     LayoutAnimation.Presets.easeInEaseOut
  17.   );
  18.   setItems(prev => prev.filter(item => item.id !== id));
  19. };
复制代码
Accordion 面板也用同一思路:toggle 时先 configureNext,完整配置里 create 用 easeInEaseOut + opacity,update 用 easeInEaseOut,然后 setExpanded(!expanded)。这样内容区平滑出现和消失,不用测量内容高度。若内容区有复杂计算或大量图片,第一次展开可能卡一下,建议控制内容区规模或用 React.memo 优化子组件。
表单“展开更多选项”同样简单:showMore 切换前调用 create(350, easeInEaseOut, opacity),再 setShowMore(!showMore),即可让邮箱、手机号、开关项等区域展开收起。

鸿蒙与 Android 的兼容启用
LayoutAnimation 在 Android 上默认关闭,需要应用入口启用。鸿蒙 RNOH 的实现沿用 Android 机制,因此也需要这段代码:
  1. import { Platform, UIManager } from 'react-native';
  2. if (
  3.   Platform.OS === 'android' &&
  4.   UIManager.setLayoutAnimationEnabledExperimental
  5. ) {
  6.   UIManager.setLayoutAnimationEnabledExperimental(true);
  7. }
复制代码
鸿蒙侧建议在 index.js 开头、AppRegistry.registerComponent 之前执行:
  1. // index.js
  2. import { UIManager, Platform } from 'react-native';
  3. if (Platform.OS === 'harmony' && UIManager.setLayoutAnimationEnabledExperimental) {
  4.   UIManager.setLayoutAnimationEnabledExperimental(true);
  5. }
  6. AppRegistry.registerComponent('MyApp', () => App);
复制代码
不加这段代码的后果是鸿蒙上 LayoutAnimation 完全没效果。原文作者曾忘记加,配置了动画、也按顺序调用了 setState,但展开收起就是没动画,排查一小时才发现原因。原文还提到,放在 App 组件里有时不生效,越早执行越稳。

鸿蒙上的几个坑
Types.keyboard 在 iOS 可配合键盘弹出收起做动画,但在鸿蒙上可能不支持。替代方案是用 KeyboardAvoidingView,或用 Animated 手动控制键盘弹出时的布局动画。
一个事件处理函数里连续触发多次布局变化时,LayoutAnimation 可能只对第一次变化生效。解决办法是每次 setState 前都调用 configureNext,并用 setTimeout 0 把两次布局变化分开,或者合并状态更新:
  1. const handleMultiple = () => {
  2.   LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
  3.   setState(prev => ({ ...prev, a: true, b: true }));
  4. };
复制代码
scrollTo 和 LayoutAnimation 也会冲突。如果在 ScrollView 或 FlatList 中先调 scrollTo,再立即触发布局动画,滚动动画可能被布局动画打断。可先等滚动完成,再触发布局变化:
  1. const scrollAndAdd = () => {
  2.   flatListRef.current.scrollToEnd();
  3.   setTimeout(() => {
  4.     LayoutAnimation.configureNext(Presets.spring);
  5.     setItems(prev => [...prev, newItem]);
  6.   }, 300);
  7. };
复制代码

setState 批量处理与动画回调
React 的 setState 是异步的,多个 setState 会被合并成一次布局变化。因此在同一个函数里连续 setShowHeader、setShowFooter、setItems 时,一次 configureNext 就能动画化所有变化,这通常是好事。若想让内容依次出现,可以配合 setTimeout 分步调用 configureNext,例如商品详情页里标题先滑入、价格随后、描述最后出现。
configureNext 还可传 onAnimationDidEnd 和 onAnimationDidFail 回调。原文作者很少用,唯一典型场景是“删除确认”:item 淡出完成后再用 onAnimationDidEnd 触发 Toast,而不是用 setTimeout 估时间。
  1. const handleDelete = (id: number) => {
  2.   LayoutAnimation.configureNext(
  3.     LayoutAnimation.Presets.easeInEaseOut,
  4.     () => {
  5.       Toast.show('已删除');
  6.     }
  7.   );
  8.   setItems(prev => prev.filter(item => item.id !== id));
  9. };
复制代码

与 Animated 的取舍
普通布局变化用 LayoutAnimation;复杂动画用 Animated。具体来说,需要循环、反转、打断的持续动画,或商品图片缩放旋转、购物车弹性动画等场景,不适合 LayoutAnimation。原文作者的项目中,约 90% 的动画需求都是展开收起、增删元素、切换显示这类简单布局变化,用 LayoutAnimation 完全够用。

踩坑清单与建议
Android/鸿蒙需要兼容代码,不启用就完全没效果;configureNext 必须在 setState 之前;keyboard 类型在鸿蒙不支持,用 KeyboardAvoidingView 或 Animated;多次布局变化要多次调用 configureNext;与 scrollTo 冲突时先等滚动完成;20 个以上元素同时动画时优先考虑 Animated。实践建议是:兼容代码放在 index.js 开头;简单展开收起首选 LayoutAnimation;持续动画用 Animated;多个 setState 尽量合并;复杂内容区注意首次展开性能;需要动画结束后执行逻辑时用 onAnimationDidEnd。
本文基于 React Native 0.84 + RNOH 0.84.1、HarmonyOS 6.0 编写,不同版本可能存在差异,以实际测试结果为准。原文中所有代码示例均已在鸿蒙设备上测试通过。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-15 12:58 , Processed in 0.026231 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部