ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter鸿蒙应用交互提示组件适配踩坑与解决方案

Flutter鸿蒙应用交互提示组件适配踩坑与解决方案 这几个月我一直在折腾一件事把以前做的一套 Flutter 组件库原封不动地搬到鸿蒙应用上。页面布局那些都好说真正让我熬夜排查的反而是看起来最不起眼的交互提示组件——Toast、SnackBar、Dialog 这一堆“弹个框、冒个泡”的东西。它们在 Android 上跑得好好的一上鸿蒙就各种不按套路出牌没弹出来、弹出来被系统拦了、切个页面就消失、动画卡得掉帧。这篇文章就是我把这些坑一个个填掉之后整理的完整记录包括组件选型、代码实现、鸿蒙平台适配、遇到的高频问题和排查思路适合正在用 Flutter 开发鸿蒙应用、或者在鸿蒙上做 UI 适配的朋友。1. 交互提示组件到底选哪套方案1.1 提示组件的分类和使用场景交互提示组件不是一个严格的技术名词它更像是一类“给用户反馈”的 UI 集合。我平时把它们分成四类轻量瞬时提示Toast、SnackBar 这类告诉用户“操作成功”“网络错误”不需要用户决策出现一两秒自己消失。确认决策型弹窗AlertDialog、BottomSheet需要用户选择“确定”还是“取消”或者从几个选项里挑一个。主动推送型提醒Banner、全局通知条在页面顶部或底部滑出来比如“版本更新”“当前处于离线状态”可能带一个跳转按钮。嵌入式状态反馈按钮 loading、列表底部加载状态、表单校验错误这些不算严格意义的“提示组件”但和提示交互紧密相关。在 Flutter 里这四类都有对应的原生实现SnackBar、showDialog、showBottomSheet、MaterialBanner再加上一个可以自定义几乎所有东西的 Overlay。你不需要引入特别复杂的第三方库Flutter 自带的 Material 组件已经覆盖了绝大多数场景而且在 Android 和鸿蒙上渲染效果几乎一致因为全部是 Flutter 自己的引擎画出来的和底层系统 UI 没有关系。1.2 为什么我优先选 Flutter 自绘而不是鸿蒙原生提示很多人在鸿蒙上用 Flutter会想着通过 MethodChannel 去调用鸿蒙的 promptAction.showToast 或者 ArkUI 的弹窗让提示看起来“更原生”。这个思路没有错但得区分场景。我的建议是应用内部的提示一律用 Flutter 自绘组件系统级提示才考虑走原生通道。原因有三个跨端一致性和调试成本。Flutter 自绘组件在 Android、iOS、鸿蒙上表现一致你只需维护一套代码。走原生通道意味着每个平台都要写一套原生逻辑而且 Flutter 和鸿蒙之间的异步通信是有延时的弹窗多了以后容易出现事件丢失、响应顺序错乱。UI 灵活度和动画控制。Flutter 自绘的 Dialog、SnackBar 可以完全自定义样式圆角、阴影、入场动画都能精确控制。鸿蒙原生弹窗虽然能做基础样式但想实现和 Flutter 完全一样的动画曲线难度直接往上翻好几倍。性能和稳定性。频繁弹 Toast 时原生通道每次都需要 Dart - Native - 原生 UI 渲染一路序列化和线程切换损耗不小。自绘组件直接在 Flutter 渲染线程完成卡顿概率更低。我踩过一个典型坑一开始为了“让鸿蒙用户觉得原生”所有 Toast 都走了原生通道。后来用户反馈弹窗有时候延迟 200 毫秒才出现在弱网环境下甚至弹了两次。排查半天发现是原生通道在页面销毁时回调丢失后来改成 Flutter 自绘 Overlay问题立刻消失。所以除非你要弹出的是“应用外”的系统级提示比如后台下载完成、来电提醒否则没必要碰原生。2. 核心组件逐个拆解属性、事件与适配细节2.1 SnackBar最常用的轻量提示SnackBar 是 Flutter 里最接近“Toast 升级版”的组件也是我用得最多的。它自带一个可以点击的 Action非常适合处理“操作完成 撤销”这种场景。基础用法很简单final snackBar SnackBar( content: const Text(文件已删除), behavior: SnackBarBehavior.floating, duration: const Duration(seconds: 3), action: SnackBarAction( label: 撤销, onPressed: () { // 执行撤销逻辑 }, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ); ScaffoldMessenger.of(context).showSnackBar(snackBar);这里有一个关键点不要用 Scaffold.of(context).showSnackBar而要用 ScaffoldMessenger.of(context).showSnackBar。区别在于 ScaffoldMessenger 是根级组件它管理的 SnackBar 不依赖某个具体页面是否还在栈里。你在 A 页面弹了一个 SnackBar马上 Navigator.push 到 B 页面SnackBar 依然会正常显示。这在鸿蒙上特别重要因为鸿蒙的手势返回和页面切换频率比 Android 还高稍不注意 SnackBar 就卡在“半路”。另外SnackBar 有队列机制。连续调用多次 showSnackBar默认会一个一个按顺序展示不会覆盖。如果我想只保留最后一条可以用clearSnackBars()或者hideCurrentSnackBar()。我一般这样处理final messenger ScaffoldMessenger.of(context); messenger ..clearSnackBars() ..showSnackBar(snackBar);在鸿蒙真机上底部如果有手势导航条SnackBar 的 floating 模式可能会和系统手势条重叠。解决办法是给 SnackBar 设置margin或者在Scaffold的 bottomNavigationBar 位置上预留 SafeArea。直接贴一个适合鸿蒙的方案SnackBar( behavior: SnackBarBehavior.floating, margin: EdgeInsets.only( left: 16, right: 16, bottom: MediaQuery.of(context).padding.bottom 16, ), ... )2.2 Dialog 与 AlertDialog需要用户决策时的标准答案弹窗类组件是交互提示里最容易被过度设计的。我的原则是只有需要用户停下当前操作、做出明确选择的场景才用 Dialog普通信息用 SnackBar 或 Toast 就够了。Flutter 的 showDialog 是一个顶层函数配合 AlertDialog 使用Futurebool? showConfirmDialog(BuildContext context) { return showDialogbool( context: context, barrierDismissible: false, // 点击遮罩不关闭防止误触 builder: (context) { return AlertDialog( title: const Text(清除缓存), content: const Text(此操作会删除所有临时下载的记录不可恢复。), actions: [ TextButton( onPressed: () Navigator.pop(context, false), child: const Text(取消), ), FilledButton( onPressed: () Navigator.pop(context, true), child: const Text(确认清除), ), ], ); }, ); }这里返回true/false给调用方让页面决定后续动作而不是在弹窗内部直接执行网络请求或者跳转这是组件职责分离的基本功。在鸿蒙上有几个细节需要注意背景遮罩的阻尼感鸿蒙上的 Dialog 动画默认比较“直接”Flutter 的 Dialog 自带 Fade Scale 效果在部分鸿蒙设备上会显得偏慢。可以通过transitionDuration: Duration(milliseconds: 150)调短但不要低于 80ms否则眼睛还没反应过来就消失了。圆角裁剪如果你自定义了 Dialog 背景记得加上clipBehavior: Clip.antiAlias否则圆角外面会出现一圈白色锯齿。这个问题在鸿蒙高分屏上尤其明显因为字体缩放比例大。内存泄漏showDialog 返回的 Future 需要正常关闭。页面在加载数据时弹了一个 loading Dialog然后用户拼命按返回键可能造成 Navigator 弹出栈异常。稳妥做法是用一个PopScope或者跟随页面生命周期关闭。2.3 全局 Toast 与自定义 OverlayFlutter 没有内置 Toast最接近的是 SnackBar但 SnackBar 依赖 ScaffoldMessenger而且会自动添加一个底部条不适合做“居中短文案”的轻提示。我自己的方案是直接用 Overlay 封装一个全局 Toast这样不依赖任何页面结构只要拿到 root context任何地方都能弹。完整实现大概是这个样子class Toast { static OverlayEntry? _entry; static void show(BuildContext context, String message) { hide(); _entry OverlayEntry( builder: (_) Positioned( top: MediaQuery.of(context).size.height * 0.5 - 40, left: 32, right: 32, child: IgnorePointer( child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.75), borderRadius: BorderRadius.circular(24), ), child: Text( message, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 14), ), ), ), ), ); Overlay.of(context).insert(_entry); Future.delayed(const Duration(seconds: 2), () hide()); } static void hide() { _entry?.remove(); _entry null; } }用的时候从页面 context 调Toast.show(context, 提交成功);这段代码有几个经验和细节OverlayEntry 必须插到 Overlay 里而Overlay.of(context)依赖 context 所在的位置。如果你在页面顶层拿到 context 去弹 Toast页面 push 到新路由后原 context 的 Overlay 被系统栈盖住Toast 自然就看不到了。解决办法是全局维护一个根 context或者在 App 根的MaterialAppbuilder 里注入一个 overlay context。后面章节我会讲具体怎么设计一个全局提示组件库。给 Toast 容器包一层IgnorePointer是为了防止飘在屏幕中央的 Toast 拦截点击事件。这一点在鸿蒙上容易踩雷——如果不加用户想点 Toast 下方的按钮结果完全点不动。连续显示多条 Toast直接用新 Toast 替换旧 Toast所以每次 show 之前先 hide。2.4 横幅提示条与底部弹层除了 SnackBar 和 Dialog我还会在重要通知场景用横幅提示条比如“切到后台联网失败”“电量低”类似系统级通知条。Flutter 里可以直接用 MaterialBanner也可以复用刚才的 Overlay 思路做一个从顶部滑入的横幅。如果使用 MaterialBanner注意它默认是嵌在 Scaffold 内部的会顶起上方内容。如果希望它悬浮在页面内容上面我建议直接自定义 Overlay 来实现代码和 Toast 类似只是位置从居中改成顶部并加一个 SlideTransition。下面给出适合鸿蒙设备的一个简化版顶部横幅void showTopBanner(BuildContext context, String message) { final overlay Overlay.of(context); late OverlayEntry entry; entry OverlayEntry( builder: (_) Positioned( top: MediaQuery.of(context).padding.top, left: 0, right: 0, child: Material( color: Colors.amber, elevation: 4, child: SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon(Icons.warning_amber_rounded), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), ), ), ), ), ); overlay.insert(entry); Future.delayed(const Duration(seconds: 3), entry.remove); }底部弹层则建议直接用showModalBottomSheet它不仅自带从底部滑入效果还支持拖拽关闭是“用户从多个选项中选择一个”场景的最佳选择。在鸿蒙上底部弹层的圆角默认是 Material 的 28如果感觉和鸿蒙原生风格不够统一可以自己包一层ClipRRect来调整圆角但别在 showModalBottomSheet 里直接设置 shape 的同时又设置 backgroundColor 为透明否则圆角会失效。3. 鸿蒙适配实录从工程创建到原生通道3.1 创建支持鸿蒙的 Flutter 工程目前我们要在鸿蒙上跑 Flutter 应用通常需要两个前提一是鸿蒙系统的开发环境二是适配 OpenHarmony 的 Flutter SDK 分支。整个流程和常规 Flutter 项目创建没有本质区别核心步骤是下载并解压适配鸿蒙的 Flutter SDK建议从官方或可信社区渠道获取不要随便在第三方网盘找省得编译到一半发现版本不对。配置环境变量FLUTTER_HOME指向该 SDK并把bin目录加到 PATH。用命令行创建项目flutter create --project-name app_feedback my_app。因为鸿蒙平台不是 Flutter 官方默认支持的 target所以需要额外添加鸿蒙平台目录。一般通过工具或手动添加harmony目录内部包含AppScope、entry/src/main/ets等结构。在鸿蒙工程中引入 Flutter 引擎的依赖把flutter_asset目录配置到 build profile 里。如果是从 Android Studio 起步创建好 Flutter 项目后再在鸿蒙开发工具DevEco Studio里打开harmony目录同步依赖后就能编译。这个流程最初看起来有点绕但只要你理解了“Flutter 引擎在鸿蒙上是一个 lib 库鸿蒙应用通过入口把 FlutterView 嵌进去”这个概念后面所有问题都能顺理成章地排查。3.2 使用 MethodChannel 调起鸿蒙原生提示虽然前面我说应用内提示尽量自绘但有些场景绕不开原生通道比如应用退到后台时需要弹出系统通知条。需要调用鸿蒙特有的提醒能力比如借助系统 UI 展示的权限弹窗。原生层收到了某些系统广播比如网络切换、存储空间不足想要主动通知 Flutter 层。Flutter 侧写法很标准class NativeNotifier { static const _channel MethodChannel(com.example.app/notifier); static Futurevoid showSystemNotification(String message) async { await _channel.invokeMethod(showNotification, {message: message}); } }鸿蒙端的实现思路是在鸿蒙工程的 EntryAbility 或特定 UIAbility 里注册这个通道。ArkTS 侧大致逻辑import { rpc } from kit.IPCKit; const channel rpc.MethodChannel(com.example.app/notifier); channel.setMethodCallHandler((call) { if (call.method showNotification) { // 调用 promptAction 的能力给出原生提示 promptAction.showToast({ message: call.arguments[message] }); } });这段代码只是一个示意不同 Flutter 鸿蒙适配分支的 API 包名会有差异但核心是抓住“Channel 注册 - 解析 method - 调用原生能力”这条链路。另外如果是原生层持续往 Flutter 抛事件比如“播放状态变化时前端要弹一个提示条”优先用 EventChannel而不是每轮都用 MethodChannel 去轮询。EventChannel 适合单向、持续的事件流交互提示组件里用到的典型场景是原生连接的蓝牙设备状态变化、下载进度变化然后触发一个提示更新。3.3 Impeller 渲染引擎对提示动画的影响Flutter 新一代渲染引擎 Impeller 在 Android 和 iOS 上已经普遍成为默认后端鸿蒙适配分支也在跟随。它最大的变化是提前编译 shader减少了动画首帧的卡顿。直观感受就是 Dialog 打开动画、SnackBar 滑动动作变得非常跟手不像 Skia 老引擎那样偶尔有一个 sharp 的抖动。但我也遇到一个反向问题在部分鸿蒙设备的模拟器上开启 Impeller 之后Overlay 里的圆角描边和阴影出现渲染异常。后来排查发现是模拟器的图形驱动对 Vulkan 支持不完整。这种情况下可以在项目启动时关闭 Impeller 再跑一遍flutter run --no-enable-impeller如果项目里确实需要这个参数记得在构建脚本和真机调试命令之间做好区分。我自己的结论是真机优先开 Impeller模拟器优先关 Impeller交互提示组件对动画流畅度敏感但比起炫酷动画更重要的是不要崩溃和不走样。4. 一个可直接抄作业的全局提示组件库4.1 项目结构与 Dart 的 part 拆分当你开始从“单个页面弹 Toast”升级到“全局统一管理提示”时代码组织就成了重头戏。我建议把提示组件做成一个独立的库放在lib/feedback/下面lib/ ├── feedback/ │ ├── app_feedback.dart │ ├── app_feedback_toast.dart │ ├── app_feedback_banner.dart │ ├── app_feedback_dialog.dart │ └── app_feedback_queue.dart └── main.dart其中app_feedback.dart作为入口对外只暴露一个统一的类比如AppFeedback.showToast、AppFeedback.showDialog。这里有一个 Dart 语言细节比较值得聊part和import的区别。很多人容易把part当成简单“把文件拆分”的方式但要注意part拆分出来的文件属于同一个库可以互相访问私有成员但必须在主文件里用part xxx.dart;声明。比如我这样写// app_feedback.dart library app_feedback; part app_feedback_toast.dart; part app_feedback_banner.dart; part app_feedback_dialog.dart; class AppFeedback { static void showToast(BuildContext context, String message) showAppToast(context, message); static void showBanner(BuildContext context, String message) showAppBanner(context, message); }被 part 进来的文件里直接用顶层函数或私有类// app_feedback_toast.dart part of app_feedback; void showAppToast(BuildContext context, String message) { // 内部实现 }这样做的优点是外部只关心AppFeedback这个入口内部实现随便拆私有变量也可以互相共享。缺点是part不能让编辑器做很好的模块化提示甚至很多格式化工具会把 part 文件认成一个部分。所以在小项目中别滥用像我这样拆出三四个文件已经足够再多就建议用 Dart 的 export 组合库。4.2 用 Cubit 管理提示消息队列提示组件如果只是一个一个独立弹窗逻辑很简单。但真实业务里经常出现这种情况用户连续点了几次保存按钮网络响应回来后一下子冒出五六个提示弹窗叠弹窗。这时候需要一个消息队列我推荐用 flutter_bloc 的 Cubit 来做理由很直接它比 StreamController 更可控比 Bloc 少了很多模板很适合“接收事件 - 依次弹出”这种逻辑。简单实现如下class FeedbackCubit extends CubitFeedbackQueueState { FeedbackCubit() : super(FeedbackQueueState.empty()); static final _queue QueueAppFeedbackItem(); void push(AppFeedbackItem item) { _queue.add(item); _process(); } void _process() { if (_queue.isEmpty || state.isShowing) return; final item _queue.removeFirst(); emit(state.copyWith(isShowing: true, currentItem: item)); // 实际弹出提示等动画结束或用户关闭后回调 _finish() } void _finish() { emit(FeedbackQueueState.empty()); _process(); } }每个AppFeedbackItem可以定义弹窗类型、文案、按钮回调、自动消失时长、重复 key。这么做的好处是当用户连续触发错误提示时不会出现“一条还没消失另一条已经盖在上面”的情况。Flutter 的 SnackBar 虽然自带队列但只对同一个 ScaffoldMessenger 有效自定义 Toast、Dialog、Banner 混在一套队列里的时候还是自己排更稳。鸿蒙上页面切换频繁使用一个全局 Cubit 管理队列可以保证从页面 A 调起的提示在页面 B 弹出来也不奇怪。4.3 全局 context 的获取与安全调用全局提示组件库里最容易被忽略的是 context 从哪来。你不可能在每个页面都传一遍 context那样这个库做出来就失去了意义。我常用的方案是在根组件里挂一个全局navigatorKeyfinal GlobalKeyNavigatorState navigatorKey GlobalKeyNavigatorState(); void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( navigatorKey: navigatorKey, home: HomePage(), ); } }然后在AppFeedback.showToast内部通过navigatorKey.currentState!.context获取全局 context。这个 context 指向的是 Navigator 的 Overlay天然在 App 顶层不受页面 push/pop 影响。这也就解决了前面提到的“切页后 Toast 消失”的问题。不过要注意如果应用还没有执行 runApp拿不到 currentState在真正调用提示之前要做空判断返回一个静默失败即可。调用方式就可以非常干净从任何页面、任何异步回调里直接await AppFeedback.showToast(数据加载完成); await AppFeedback.showConfirmDialog(确定删除吗, onConfirm: () async { ... });5. 高频问题与避坑清单5.1 Navigator 切换页面后提示状态怎么不被吞掉很多 Flutter 开发者问过同一个问题用 Overlay 实现的 Toast在Navigator.push新页面之后看不到了是不是 Overlay 状态丢了其实不是状态丢失而是你使用了“当前页面 context 对应的 Overlay”它属于当前路由。当前路由被新路由覆盖后其 Overlay 也被系统藏在栈底层所以新的提示显示在“旧页面”上自然看不见。解决办法有三个使用全局navigatorKey获取根 Overlay所有提示都插在根 Overlay 上不随页面切换消失。SnackBar 用ScaffoldMessenger.of(context)而不是Scaffold.of(context)因为 ScaffoldMessenger 是 App 根级的它会自动把 SnackBar 显示在当前的 Scaffold 上。如果用了依赖注入的全局 Dialog 容器确保它挂在MaterialApp.builder或home的父节点上不要挂在某个具体页面树里。这一点在鸿蒙上尤其重要因为鸿蒙返回手势很顺滑用户频繁切页是常态。我就是一开始偷懒把 Toast 的 Overlay 绑到了首页的 context结果从首页跳详情页再返回途中点击按钮弹出的提示全部“聊胜于无”最后改了全局 navigatorKey 才彻底解决。5.2 TabBar 点击水波纹动画影响提示触发这个问题属于交互提示里比较细枝末节的如果页面使用 TabBar用户点击 Tab 切换时默认会有水波纹动画。当这个动画和 Overlay 上的 Banner 或 Toast 同时出现时水波纹会把 Toast 的背景盖掉一层看起来提示异常闪烁。更严重的是如果自定义了 TabBar 监听手势可能会在切换过程中误触发提示队列。取消 TabBar 点击水波纹动画非常简单TabBar( splashFactory: NoSplash.splashFactory, tabs: const [ Tab(text: 首页), Tab(text: 消息), ], )如果用的是自定义 TabBar推荐把所有点击处理都放在onTap回调里不要在notificationListener里处理切换提示。同时在TabController.addListener回调里不要直接弹全局提示先判断当前 tab index 是否已经切换成功否则用户快速左右滑动 Tab 时会出现提示刷屏。5.3 鸿蒙网络请求异常导致的提示件失灵在鸿蒙上调试 Flutter 应用时最让人头疼的往往是网络层的问题比如错误码 2300056。现象很典型同样一段代码在 Android 上请求正常鸿蒙上却直接抛出这个错误。它不是 Flutter 层能捕获的业务异常而是鸿蒙网络库的调用错误常见根因包括应用没有配置网络权限或 Android 的AndroidManifest.xml有权限但鸿蒙的 module.json5 缺了ohos.permission.INTERNET。使用了不受信任的自签名证书鸿蒙默认的安全校验策略比 Android 更严格。域名没有在后台配置网络白名单被安全组件拦截。这类问题如果处理不好你在 Flutter 侧写的 Toast 提示只会显示一条“请求失败”根本定位不到是网络问题。我的处理方式是在网络工具类里统一捕获错误码如果错误包含2300056就在提示组件里显示“请检查当前网络环境或稍后重试”同时在 debug 模式下额外打印原始错误和错误码而不是只提示通用文案。抓包工具在这时候很有用能直接确认请求有没有发出去、对方返回了什么不要只盯着 Flutter 层看。5.4 打包构建时提示组件相关的断言错误Flutter 在打包鸿蒙应用时偶发遇到java.lang.AssertionError: java.lang.Exception: could not close ...这类构建断言错误。出现这个问题时项目里的交互提示组件往往不是直接原因而是某个插件在原生侧的资源没有正确发布。我的排查顺序是先看是否是 R8 混淆把某个自定义NotificationBar的类给吞了尝试关闭混淆跑一次 release。再检查 Flutter 插件和鸿蒙 SDK 的版本是否匹配很多第三方插件的鸿蒙适配并不完善打包时资源合并失败就会被错误信息误导到提示组件相关代码。最后如果代码里大量使用part拆分文件确认 part 声明的文件名和磁盘路径完全一致大小写也不要忽略。Mac 上大小写不敏感容易放过问题Windows/Linux 打包机上一跑就暴露。说实话这类问题没有万能药只能靠解压构建产物、看 AGP 日志一步步剥。建议把构建日志输出到文件不要只看终端最后二十行。我在解决一个 banner 相关崩溃时就是因为只盯着最末行的 AssertionError没发现在此之前原生产出物缺少一个banner_config.json资源文件。5.5 模拟器与真机的提示表现差异鸿蒙模拟器和真机在 Overlay 显示上存在细微差异。模拟器通常没有刘海屏和安全区MediaQuery.padding.top为 0导致顶部 Banner 在模拟器上看起来正常但上了真机直接被时间状态栏盖住。所以我所有顶部提示组件都会在Positioned的 top 上加上MediaQuery.of(context).padding.top并且容器内部再用一个SafeArea(bottom: false)包一层。真机调试时我推荐使用鸿蒙的无线调试功能把手机和电脑连同一个局域网然后用 DevEco Studio 的无线调试工具连接。相比插 USB无线调试更贴近真实用户场景特别是测试“切到后台再回到前台”时提示组件是否会被系统回收。真机测试弹窗时记得把“后台弹出界面”这个权限打开否则系统级提示几乎一试一个不灵。6. 写在最后的一句经验交互提示组件看起来每一小块都很简单真正把它们拼成系统性组件库的时候坑点全在“时序”和“作用域”上。我个人的体会是与其到处找各种高级动画组件不如先把ScaffoldMessenger、Overlay、navigatorKey这三件事吃透。如果你在鸿蒙上连续遇到提示不显示、动画乱跳、状态错乱回头检查一下是不是 context 选错了地方、队列逻辑没做好很多问题能迎刃而解。这个组件库还可以继续扩展的方向是把鸿蒙原生的来电通知、语音播报能力通过 EventChannel 接进来做成一个同时覆盖 App 内外、轻量且统一的通知体系。
返回列表