尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Flutter鸿蒙适配实战:文档应用布局核心与性能调优

Flutter鸿蒙适配实战:文档应用布局核心与性能调优 我最早接触 Flutter for OpenHarmony是接手一个交互式文档应用的移植。团队手里的原始版本是基于 Flutter 写的功能很完整——富文本阅读、目录锚点导航、代码高亮、搜索定位、深色模式切换都有目标是把它平移进 OpenHarmony 生态。坦白说刚开始心理预期不高毕竟 OpenHarmony 对外主推的是 ArkUI 自研渲染体系Flutter 在这个平台上到底能跑出几成体验大家心里都没底。真正跑起来之后发现布局引擎的整体能力比想象中完整真正难受的点全集中在细节上——文本测量、字体回退、PlatformView 嵌入、原生插件适配、构建链路的版本冲突。而这些细节里布局核心恰恰又是交互式文档应用的命门。这篇文章不写 Hello World直接聊我在 OpenHarmony 上把 Flutter 文档应用从零搭起来的过程。重点覆盖四部分工程搭建链路、三种典型文档布局的落地方式、导航与状态保持、原生桥接与排错经验。适合正在做 Flutter 鸿蒙适配、或者准备把已有 Flutter 应用迁移到 OpenHarmony 的开发者参考尤其是那些被布局细节和构建报错卡住的人。1. 为什么文档应用是 Flutter 上鸿蒙的第一块试金石先聊一个很多人忽略的问题同样是 Flutter 应用为什么偏偏文档类应用最容易暴露鸿蒙适配的短板因为文档应用的 UI 形态和普通工具类应用不一样它天生就对布局引擎提出更高的要求。1.1 文档应用对布局能力的三重考验第一重考验是文本排版。文档应用不是简单地把一段 String 丢到 Text 控件里完事。它要处理多级标题、不同字号和行高、列表缩进、引用的左边框、代码块的灰色底、表格的列宽分配。这些内容在 Flutter 里全部依赖 TextPainter、RichText、TextSpan 这一套渲染体系。而在 OpenHarmony 上的 Flutter 引擎文本测量这一环与 Android/iOS 有微妙差异中文字体的 fallback 链、字重映射、行高表现都可能和你预期不一致。我实测遇到过同一篇 Markdown部分标题在鸿蒙设备上行高压缩、字重丢失的情况排查到最后是系统字体回退路径不同导致的。第二重考验是响应式布局。文档应用的使用场景很杂手机竖屏看正文、平板横屏分栏阅读、折叠屏展开三分栏。这就逼着你在布局代码里对 MediaQuery、LayoutBuilder、宽高约束做大量判断。而 OpenHarmony 的窗口管理和安全区策略与其他平台不一样横竖屏切换、分屏比例变化、折叠屏展平时的窗口尺寸变化频率比手机上高得多。布局如果不做约束收敛分屏一拖就很容易出现 RenderFlex overflow 的黄黑条纹。第三重考验是混合渲染。文档里嵌图片、嵌 PDF、嵌视频已经很常见。纯 Flutter 渲染图片问题不大但 PDF 预览、复杂表格的手势缩放很多时候必须借助原生控件。这就绕不开 PlatformView。OpenHarmony 上 PlatformView 的底层实现虽然参考了 Android 的架构但嵌入策略、图层合成方式、触摸事件派发的细节差异会让很多在 Android 上跑得好好的代码在鸿蒙上报黑屏、闪烁或手势穿透。1.2 交互式文档的典型交互栈这里说的“交互式”不只是能翻页。它通常包含几层能力拼起来目录导航点击目录项正文平滑滚动到对应章节反向滚动也要联动高亮目录全文搜索输入关键词正文中高亮所有匹配片段支持逐个跳转字号缩放用户调大字体正文重新排版行宽、分页跟着变深色模式切换主题时整页配色重算代码块高亮配色也要跟着换代码块操作点击复制代码、横向滑动查看长行这些交互全部建立在布局系统之上。导航联动是 Scrollable 与 RenderObject 的坐标换算字号缩放依赖文本重排触发重新布局深色模式是整棵 Widget 树的配置重建。换句话说文档应用就是布局核心的“压力测试器”布局引擎任何一点不靠谱在普通应用里可能只是个轻微卡顿在文档应用里直接就表现为功能失效。1.3 Flutter 在 OpenHarmony 上的运行路线在聊具体布局之前有必要先建立整体认知。目前 Flutter 在 OpenHarmony 上运行走的并不是谷歌官方分支而是社区适配层提供的 flutter_flutter、flutter_engine、flutter_packages 这一套仓库体系。它们的工作方式是把 Flutter 引擎编译成 OpenHarmony 可加载的 native 模块然后在 OpenHarmony 的 Ability 框架里启动 Flutter 引擎并把 FlutterView 挂载到页面窗口上。这套路线决定了两件事第一Flutter 的布局管线、渲染管线、Widget 体系基本是原样的你在其他平台学的布局知识在鸿蒙上依然成立第二平台通道Platform Channel这一层需要重新适配因为 OpenHarmony 既没有 Android 的 Java 层也没有 iOS 的 Objective-C 层MethodChannel、EventChannel、PlatformView 都需要走 OpenHarmony 的 native API 实现一遍。想清楚这个底层逻辑后面遇到的很多问题就能定位到正确的层级布局问题大概率在 Flutter 自身或文本渲染层桥接问题大概率在平台通道的 ohos 实现构建问题大概率在工程配置或插件版本。2. 工程搭建链路从 Flutter SDK 到 ohos 工程目录这一步没什么炫技空间但版本匹配和目录结构不对后面所有布局代码都跑不起来。我把完整的工程搭建链路拆开讲重点说容易翻车的几个点。2.1 环境准备与版本对齐首先保证两样东西的版本是配对的OpenHarmony SDK 和 Flutter SDK。社区适配分支通常会说明它基于哪个 Flutter 版本比如基于 Flutter 3.7 或 3.22 之类的基线版本。理论上你用任意 Flutter 版本写 Widget 代码没问题但真正编译进 OpenHarmony 时需要的是适配后的引擎和工具链版本不对会在编译阶段直接报错而且是那种让人摸不着头脑的符号找不到。建议做法是先锁定一个社区验证过的组合安装对应版本的 DevEco Studio下载配套的 OpenHarmony SDK再准备适配版 Flutter SDK。在项目根目录的 local.properties 里显式指定sdk.dir/path/to/your/ohos-sdk flutter.sdk/path/to/your/flutter-sdk这个文件一定要确认被读取到了。我遇到过明明配置了路径但构建时还是走了默认 SDK 的情况归根结底是 IDE 的缓存没刷新清理后重新打开项目才正常。2.2 创建 Flutter 工程的 ohos 平台目录如果你是从零开始的 Flutter 工程创建命令需要带上 ohos 平台参数或者先在 DevEco Studio 里建一个 OpenHarmony 工程再把 Flutter 模块嵌进去。采用 Flutter 侧创建、后续补充 ohos 目录也完全可以。创建完成后工程结构大概长这样project/ ├── lib/ # Flutter 业务代码 ├── ohos/ │ ├── entry/ # OpenHarmony 入口模块 │ ├── build-profile.json5 │ └── oh-package.json5 ├── pubspec.yaml └── local.properties这里最难理解的是 ohos 目录与 Flutter 业务代码的关系。OpenHarmony 的应用入口是一个 Ability适配层提供了类似 FlutterAbility 的基类。你的入口 Ability 需要继承这个基类并在合适的生命周奇里初始化 Flutter 引擎。真正干活的 UI 几乎全在 Flutter 侧ohos 侧更像是一个宿主壳负责打开窗口、承载 FlutterView、处理系统级事件。这个壳的代码量不大但有一点必须处理好Ability 的生命周期要同步给 Flutter 侧。页面退到后台、窗口尺寸变化、获得和失去焦点这些事件最好通过平台通道转发给 Flutter否则布局和状态很容易出现“跟手但不同步”的诡异现象。2.3 最容易翻车的版本与仓库问题工程配置里最常见的两个报错一个是插件版本过低一个是 Flutter Gradle 插件被命令式地 apply。插件版本过低的问题很好理解OpenHarmony 适配侧对插件的支持是分批次完善的老的 flutter pub 包可能只实现了 Android/iOS 平台没有 ohos 目录。你在 pubspec.yaml 里引用后构建时插件注册发现找不到 OpenHarmony 实现直接抛异常。排查方法是看插件包目录结构里有没有 ohos 子目录没有就意味着需要找替代包或者自己在 ohos/entry 里补实现。另一个报错就是热词里那个 “you are applying flutters main gradle plugin imperatively using the apply script”。这个问题我曾经在鸿蒙工程里也撞上过项目在保留 Android 构建配置的同时引入了 ohos 构建两份配置共用一套插件引用结果 Gradle 把同一个 Flutter 插件脚本执行了两遍。排查思路是检查 settings.gradle 里的插件声明方式确保用的是插件 DSL 而不是命令式 apply script同时确认 ohos 工程的构建脚本没有把 Android 侧的插件逻辑整个拷贝过来。提示遇到构建报错不要先怀疑代码先检查工程里是否残留了另一套平台的 gradle 配置。跨平台工程很多诡异报错都是“两套构建体系互相干扰”。3. Flutter 布局核心三种典型文档布局的鸿蒙落地进入正题。这一节是整篇文章的核心我按文档应用最常用的三种布局形态来讲分栏阅读、富文本排版、自定义悬浮交互布局。每种都给出我的实现思路和鸿蒙实机上的表现。3.1 分栏阅读布局LayoutBuilder 驱动的自适应排版交互式文档应用最常见的形态是左侧目录、右侧正文的分栏布局。在 iPad 或平板上目录栏固定宽度正文占据剩余空间在手机上目录栏收起变成顶部抽屉或者底部弹出的面板。实现分栏布局的核心是约束感知。我用 LayoutBuilder 包住最外层根据 constraints.maxWidth 决定渲染模式LayoutBuilder(builder: (context, constraints) { final isWide constraints.maxWidth 720; return Row( children: [ if (isWide) const SizedBox(width: 240, child: DocSidebar()), Expanded( child: isWide ? const DocReader() : const MobileDocShell(), ), ], ); })这段代码很简单但它背后有一个值得注意的细节Row 在收到 loose constraints 时会如何处理子元素的宽度。OpenHarmony 上窗口尺寸变化频繁尤其是折叠屏展开瞬间约束会从手机宽度突变为平板宽度。如果布局逻辑直接依赖 MediaQuery.of(context).size很容易拿到过期的尺寸。LayoutBuilder 的优势在于它感知的是当前约束而不是全局窗口尺寸从根源上避免了“尺寸信息滞后一拍”的问题。另一个坑是安全区。OpenHarmony 设备普遍存在底部导航条、顶部状态栏区域部分机型还有手势条。如果文档正文滚到底部时内容被导航条遮住阅读体验会非常差。我的做法是在根布局里统一读取 MediaQuery.padding把 SafeArea 应用到分栏容器的最外层而不是每个叶子控件都加一遍 padding。这样目录栏、正文栏、底部工具条都共享同一套安全区偏移不会出现目录栏留了底、正文栏又留一遍的双重重叠。3.2 富文本排版布局文本重排、行高与字体回退文档正文的排版质量直接决定这个应用能不能被用户接受。Flutter 侧负责排版的是 TextPainter 整套机制但真正影响观感的是这几点字号缩放。鸿蒙系统允许用户在设置里调整字体大小对应到 Flutter 就是 MediaQuery.textScaler。文档应用的痛点在于代码块、表格这类等宽排版内容不应该跟着正文做一个比例的字号缩放否则会破坏对齐。我做的处理是在全局设置 textScaler 后代码块和表格内部用自己的固定 fontSize 覆盖并且把代码块的横向滚动作为兜底方案。这样用户把正文放到很大时代码块依然保持可读的对齐结构。行高与字体回退。这是我在鸿蒙上踩得最深的一个坑。同样的文本在 Android 上显示正常在 OpenHarmony 上某些字体的行高计算会略有偏差。排查后发现是字体 fallback 链不一样系统在无法命中某个 fontFamily 时会按自己的规则去匹配替代字体替代字体的 metrics 不同行高就跟着变了。解决办法是显式指定 fontFamilyFallback并且为正文统一设置一个合理的 height 值让行高不至于被系统字体包里的 metrics 带偏。表格和代码块溢出。文档里经常出现超出屏幕宽度的代码和表格正确的做法不是缩小字号硬塞进屏幕而是保持原始尺寸允许横向滚动。我用 SingleChildScrollView 包住横向内容再配合一个“可横向拖动”的手势提示实测在鸿蒙上滚动流畅度没问题。这里有个容易被忽略的点横向滚动容器必须显式设置 constraints否则在 Row 里会被拉伸成无限宽出现布局异常。3.3 悬浮交互布局CustomMultiChildLayout 的实战价值文档阅读器里通常需要一个悬浮工具栏选中文字后的高亮/复制按钮、阅读进度百分比、回到顶部的悬浮按钮。这些元素如果直接用 Stack 加 Positioned布局代码会变得非常零散尤其当悬浮元素需要跟随滚动状态而改变位置时Stack 模式很难维护。我建议用 CustomMultiChildLayout 加上自定义 LayoutDelegate 来管理这类悬浮层。核心思路是在单次布局过程中根据主内容区的约束和滚动偏移量计算出悬浮元素应该出现的位置然后通过 delegate 里的 positionChild 方法一次性摆放到位。class DocOverlayLayoutDelegate extends MultiChildLayoutDelegate { DocOverlayLayoutDelegate({required this.scrollOffset}); final double scrollOffset; override void performLayout(Size size) { if (hasChild(fab)) { layoutChild(fab, BoxConstraints.tight(const Size(48, 48))); positionChild(fab, Offset(size.width - 56, size.height - 160)); } if (hasChild(progress)) { layoutChild(progress, BoxConstraints.tight(const Size(120, 32))); positionChild(progress, Offset(size.width - 160, size.height - 96)); } } override bool shouldRelayout(DocOverlayLayoutDelegate oldDelegate) oldDelegate.scrollOffset ! scrollOffset; }这样做的好处是布局逻辑集中悬浮元素的位置只依赖一个 scrollOffset 参数滚动事件驱动它重新布局即可。鸿蒙上滚动事件的回传频率和 Android 一致没有出现帧率掉队的情况。唯一要记住的是每次滚动回调都会触发一次 relayout所以要确保 delegate 里的计算足够轻量不要在 performLayout 里做字符串拼接或复杂计算。4. 交互骨架导航状态保持、锚点滚动与布局联动布局只是骨架交互才是血肉。文档应用的交互集中在导航切换、锚点定位、主题/字号变换这三大块每一块背后都牵扯到布局重算。这里我总结了一套在 OpenHarmony 上经过实测的方案。4.1 导航切换不丢状态的三板斧很多 Flutter 开发者在做底部 Tab 切换时默认用 Navigator.push 一套接一套地压页面结果发现切走再切回来滚动位置、搜索关键词、目录展开状态全丢了。文档应用里这是不可接受的用户看一篇长文切出去查个单词回来得回到原来的位置。我的方案第一板斧是底部主导航不用 Navigator而是用 IndexedStack 保存三个子页面的实例。IndexedStack 会把所有子页面都保留在树上只是用 Visibility 控制显示切换时完全不会触发重建。代价是所有子页面的状态都常驻内存对文档应用这种三个 Tab 的量级来说内存完全扛得住。第二板斧在正文页面内部用 AutomaticKeepAliveClientMixin 保证滚动位置不丢失。配合 ScrollController甚至可以在页面被系统回收后恢复上次的偏移量。第三板斧全局唯一的页面容器。不要在 Tab 之间各自独立维护一套 Navigator否则从正文页 push 一个详情页再返回Tab 状态会被重建。统一用一个 Navigator在 App 根部维护保证任何页面跳转操作都不会触及底层 Tab 的 State。4.2 锚点定位目录点击、正文滚动、双向联动目录是文档应用最核心的交互之一。点击左侧目录的一项正文要滚动到对应章节的顶部反过来正文滚动经过某一章节时目录的高亮项要同步更新。实现原理不复杂但细节很考验布局功底。点击目录跳转我使用的是 Scrollable.ensureVisible 加 GlobalKey 的方式。每个章节的标题 Widget 挂一个 GlobalKey目录点击时拿到对应 context调用 ensureVisible。这里有两个注意点第一章节 Widget 必须是已经构建出来的。如果正文用的 ListView.builder 懒加载远处的章节还没构建GlobalKey 对应的 context 是空的。我的处理方式是把文档章节拆成一个数组正文用 ListView 渲染完整列表而不是懒加载模式。文档的章节数量通常在几十到几百之间全量渲染的成本比想象中的低换来的是锚点定位的精确和稳定。第二滚动联动要防抖。正文滚动时会不断触发 ScrollController 监听如果每次都去计算当前落在哪个章节再通知目录高亮性能会有明显压力。我的做法是用节流记录上次高亮的章节索引只有当索引真的变化时才通知目录刷新。void onScroll(double offset) { final currentIndex _findCurrentSection(offset); if (currentIndex _lastSectionIndex) return; setState(() _activeSection currentIndex); }4.3 主题切换与字号缩放时的布局重算深色模式和字号缩放都会触发全局布局重算。文档应用里最容易出问题的是代码高亮和 Markdown 引用的配色。主题切换时如果代码块的高亮颜色是根据主题色动态生成的必须确保在 build 阶段根据当前 ThemeMode 重新计算而不是在初始化时缓存一份。否则用户切换主题后代码块的颜色还是旧的看起来比正文还亮体验非常割裂。字号缩放方面我强烈建议在正文区域用一个统一的 TextScaler并把它作为 InheritedWidget 向下传递。这样每个文本控件都能根据全局缩放系数重新排版同时代码块、表格内部可以单独设置缩放豁免。实测证明这种模式下鸿蒙上字号切换时文本重排的帧率能稳定在 50 fps 以上肉眼感知不到闪烁。另一个容易忽略的细节是字号缩放会导致正文高度变化进而影响 ScrollController 偏移量。用户把字体调大后原来在 5000 像素处的位置新排版下可能对应 6000 像素。处理方式是在缩放比例变化前记录当前章节索引缩放完成后重新定位到这个章节而不是继续停留在原来的像素偏移量。5. 原生能力的桥接与复用PlatformView 和 EventChannel 的鸿蒙适配纯 Flutter 在文档应用里能覆盖 80% 的界面需求但 PDF 预览、大图缩放、本地文件读取这类能力最终还是得交给原生实现。这一节聊我在 OpenHarmony 上桥接原生能力的实践以及插件适配流程中容易踩的坑。5.1 PlatformView 的三种嵌入策略怎么选OpenHarmony 的 PlatformView 体系基本承袭了 Android 的那套思路核心问题依然是原生 View 如何与 Flutter 渲染图层合成。社区实现里有三种策略VirtualDisplay 虚拟屏模式把原生页面渲染到一块虚拟屏幕上再合成进 Flutter 图层。兼容性最好但触摸事件需要手工转发响应延迟略高。TextureLayer 纹理模式原生页面输出纹理Flutter 端以纹理形式渲染。流畅度最好但对原生 View 的实现有要求部分复杂原生控件可能出纹理异常。Hybrid 混合模式简单场景用虚拟屏保证兼容复杂交互用纹理保证流畅由开发者在代码里手动指定。我的选择策略非常务实PDF 预览这种静态为主、手势交给原生的场景用 VirtualDisplay 模式稳图片浏览器这种需要大量滚动和缩放的场景用 TextureLayer 模式跟手。不要在代码里写死一种策略最好通过平台通道动态下发布局配置让原生侧根据传入参数选择策略。UiKitView( viewType: pdf_view, creationParams: {path: filePath, renderMode: virtualDisplay}, onPlatformViewCreated: onCreated, )5.2 MethodChannel 与 EventChannel 的典型用法文档应用里我常用的方法通道就两类一类是文件 IO 类操作比如读取本地文档内容、写入阅读进度另一类是系统能力调用比如复制文本到剪贴板、调起分享面板。MethodChannel 在这两种场景下都很顺手。static const MethodChannel _channel MethodChannel(com.example.docs/file_io); FutureString readLocalDoc(String path) async { final result await _channel.invokeMethod(readFile, {path: path}); return result as String; }EventChannel 则用于原生向 Flutter 侧推送事件。我的典型场景是监听文件下载进度。文档应用有时需要从网络拉取大文件进度事件由原生层发起Flutter 侧只要订阅广播流即可static const EventChannel _downloadChannel EventChannel(com.example.docs/download_status); _downloadChannel.receiveBroadcastStream().listen((event) { // 更新进度条 UI });这里有一个使用上的细节EventChannel 的订阅在页面销毁时一定要 cancel。文档应用的页面栈层级较深如果每个页面都订阅同一个 EventChannel 而忘了取消会出现事件重复分发进度条跳变的诡异问题。5.3 插件适配鸿蒙的标准流程如果你在 pubspec 里引用的第三方插件没有 ohos 实现别急着换掉它。参考市面上已经完成鸿蒙适配的插件的做法自己补一个 ohos 实现是完全可行的。适配流程其实有固定套路第一步查看插件源码找到 Android 侧的 PlatformViewFactory 或 MethodCallHandler 实现 第二步在插件工程的根目录下新建 ohos 目录按照社区适配规范写一份对应的 Dart 接口和 ohos 原生实现 第三步在插件注册入口把新的 ohos 实现加载进来 第四步用连上真机的调试环境做端到端验证确认方法能调通、事件能回传。整个流程里最耗时的不是写代码而是梳理原生 API 的在鸿蒙上的对等实现。有些 Android API 在 OpenHarmony 上有直接对应有些则需要绕路。做之前先查清楚原生能力在鸿蒙上的系统 API 文档能节省大量试错时间。注意适配插件时优先保证方法通道名、参数结构、返回值类型与 Android/iOS 完全一致。这样业务代码无需改动插件适配就是对上层透明的。6. 性能调优与构建排错实测遇到的几个硬茬最后聊性能调优和构建排错。文档应用在布局上的性能瓶颈通常不在计算量而在不必要的重建和合成。构建层面的问题则集中在资源处理和插件脚本上。6.1 布局性能把“该重建的”和“不该重建的”分开文档应用的页面结构复杂如果不做约束一次字号缩放或主题切换可能导致整棵 Widget 树重建成本极高。我的调优核心是三层隔离第一层是 RepaintBoundary。在目录栏、正文区、工具栏三块分别包一层 RepaintBoundary。当正文滚动时目录栏和工具栏不需要重绘当目录高亮变化时正文不需要重绘。这个收益在 OpenHarmony 上非常明显合成层级的独立能大幅减少 GPU 片的绘制压力。第二层是 const 构造。代码里凡是能写成 const 的 Widget一律加 const。文档应用的很多静态组件比如分隔线、图标按钮、标题样式都不需要重建。const 构造让 Flutter 在建树阶段直接跳过这些子树。第三层是列表项粒度。文档正文如果拆成一条条章节再拼成 ListView每个章节的 build 都应该只依赖自身数据不要让它读取全局的滚动偏移或搜索状态。搜索高亮状态的传递只通过独立的 InheritedWidget 向下分发。这样搜索关键词变化时只有真正包含匹配项的章节会重建。6.2 构建期典型报错的完整排查链路第一类报错是资源文件输入流无法关闭日志里出现 could not close input 之类的错误信息。这类问题我遇到时的第一反应是不去查业务代码而是把注意力放在构建阶段。完整排查链路是先从日志定位是哪个构建任务抛的异常通常是资源归档或打包环节再检查构建机器上是否有杀毒软件或文件同步工具锁定了临时目录最后看项目的缓存目录是否被占用清理 build 目录和临时目录后重试。大多数情况下这是文件句柄冲突或资源重复引用导致的问题而不是代码缺陷。第二类报错是关于 Flutter Gradle 插件被命令式 apply 的问题。在跨平台工程里如果你原本有 Android 构建配置又引入了 ohos 构建配置两份配置可能会共享一套 Gradle 插件脚本。解决方案是统一插件声明方式在 settings.gradle 里用 pluginManagement 声明插件版本然后在模块级 build.gradle 里按需 apply。不要在项目级脚本里拷贝命令式 apply script否则多模块评估时会重复执行。我自己处理这类问题的经验是跨平台构建问题大多数不是代码问题而是工程配置里的“平台残留”。每引入一个新平台都要回头清一遍其他平台的构建脚本残留这个习惯能省掉一半的排查时间。6.3 发布前的布局自检清单项目临近交付时我习惯做一轮针对性的布局自检这里列出来供你参考最小窗口和最大窗口下分栏布局是否都能正常渲染有没有 overflow系统字体设为超大时正文是否还能完整阅读表格和代码块是否保持对齐深色模式切换后代码高亮、引用块、分割线的对比度是否达标折叠屏从展开到折叠布局是否跟随约束平滑变化不出现白屏或跳动底部导航切换后滚动位置和搜索状态是否保持打开一个包含大量图片和代码块的文档滚动帧率是否稳定这一套自检做完基本可以保证布局核心在鸿蒙设备上的表现是稳定的。最后分享一个我自己的实战体会Flutter 在 OpenHarmony 上开发最大的挑战不是 API 不会用而是调试链路比 Android 长。很多布局问题在模拟器上不明显一到真机上就暴露。建议从一开始就坚持真机调试把模拟器仅当作截图工具。另外布局相关的代码尽量集中管理不要散落在各个业务文件里文档应用这种复杂布局状态下集中管理约束和主题配置能极大降低后期的维护成本。这套项目做完后我得出的结论是OpenHarmony 上的 Flutter 已经完全具备承担复杂交互应用的能力难点只是需要你用对待一个“新平台”的认真程度去面对它而不是拿 Android 的经验生搬硬套。
返回列表