画师接稿这个赛道,最头疼的从来都不是“画功”,而是“渠道”和“流程”。画师散落在微博、B站、小红书,甲方散布在各种QQ群、约稿平台、甚至闲鱼,两边对接全靠私信来回拉扯,需求的确认、报价、改稿、交付、结算,每一个环节都在烧精力。我一直在想,能不能做一个真正属于这个群体的工具型平台,把“找单子、谈需求、传稿件、收尾款”整套流程拉到同一条线上。而“跨端”是这个平台绕不开的第一道坎——甲方可能用iPhone,画师可能用安卓平板,Windows电脑绘图、Mac看稿子,再加上近两年设备形态越来越多的 OpenHarmony 生态,如果每个端都做原生,一个小团队根本维护不过来。
所以我把技术栈定在了 Flutter × OpenHarmony 这套组合上。不是追新,是被现实逼出来的方案。Flutter 一套代码覆盖 iOS、Android、桌面端,而 OpenHarmony 这边有 SIG 组持续维护的 Flutter 移植分支,意味着我可以在同一个 Dart 代码库里把这套接稿平台铺到几乎所有主流设备上。这篇文章我想把这大半年来的实战过程完整复盘一遍:从选型逻辑、架构设计,到 Flutter 在 OpenHarmony 上的移植适配,再到打包上线时踩过的那些坑。如果你是接稿类应用的开发者,或者正在纠结要不要入 OpenHarmony 的 Flutter 坑,这篇应该能帮你省掉不少弯路。
1. 项目选型:为什么是 Flutter × OpenHarmony
1.1 画师接稿平台到底需要覆盖哪些端
先盘一下实际业务场景里的设备分布。画师这个群体有个特点:创作设备和工作设备是分离的。主力绘画工具是 iPad + Apple Pencil 或者 Windows 数位屏,日常沟通用手机,传图改稿偶尔开电脑。甲方那边就更多样了,很多约稿的甲方就是普通用户,手机从 iPhone 到各种安卓品牌都有,也有不少习惯用平板看图的。
这就意味着接稿平台至少要覆盖 iOS、Android、Windows、macOS 四个主流平台,现在还得加上 OpenHarmony 设备。如果走原生路线,iOS 和 Android 各养一个团队已经是很大成本,桌面端还得再来两套代码,总共四套代码库,对于独立开发者或者小团队来说,迭代速度会被拖死。Flutter 的优势恰好在这个点上:一套 Dart 代码渲染 UI,业务逻辑全共享,Windows 和 macOS 通过桌面端支持,移动端 iOS/Android 是主战场。再加上 OpenHarmony 的 Flutter 移植分支,这个矩阵基本就补齐了。
还有个容易忽略的因素是“长尾设备”。很多画师会买国产的墨水屏阅读器、OpenHarmony 开发板改装的小主机,这些设备跑不了完整的原生应用,但 Flutter 的自绘引擎优势在这里反而成了亮点——因为 UI 不依赖系统控件,跨平台一致性非常高,在 OpenHarmony 上跑出来的界面能跟 iOS 上几乎一模一样。
1.2 Flutter 的渲染机制为什么适合创作类应用
市面上做界面展示的开源方案不少,React Native、Compose Multiplatform 都是备选。但具体到接稿平台这个场景,Flutter 有一个非常核心的优势:Skia 自绘引擎 + 统一的渲染管线。画师平台本质上是“图片密集型”应用,画廊页要高清展示作品大图,聊天里要发预览图,订单详情要对比改稿前后差异,这些场景对渲染一致性和流畅度的要求非常高。
Flutter 不通过原生控件桥接,而是自己直接画每一帧 UI。这意味着在 Android 上看到的图片质量、圆角效果、阴影渲染,在 Windows 上、在 OpenHarmony 上看到的是一模一样的,不会出现某平台控件风格突兀、图片色彩空间被系统改掉的问题。对画师这种对色彩和精度极其敏感的群体来说,这种一致性的体验价值很高。
很多开发者被“谷歌放弃了 Flutter”这种说法搞得很焦虑,其实这是对 2024 年谷歌组织架构调整的误读。谷歌只是把 Flutter 团队划入了其他部门,Flutter 本身仍然在正常迭代,v3.22、v3.24 的发布节奏也证明了这一点。对于我这种小团队来说,选 Flutter 看的是它背后扎实的社区生态和 OpenHarmony SIG 的持续跟进,而不是哪家公司某个季度的组织变动。
1.3 OpenHarmony 生态的接入方式与风险控制
OpenHarmony 不是 HarmonyOS,这个概念要先理清。OpenHarmony 是开源项目,任何人都可以拿源码编译自己的系统版本;HarmonyOS 是华为基于 OpenHarmony 做的商业发行版。对我们开发者来说,接入 OpenHarmony 意味着应用可以跑在开源鸿蒙体系的各种设备上,包括开发板、开源手机、平板以及一些行业定制设备。
Flutter 在 OpenHarmony 上的官方适配路径,是通过 Gitee 上的 OpenHarmony-SIG 组织维护的 flutter_flutter、flutter_engine、flutter_packages 三个核心仓库。这套分支基本是跟着 Flutter 上游版本走的,虽然会比原生 Flutter 落后几个小版本,但胜在可用性已经达到生产级别。我用的是 flutter_flutter 的分支版本,主要用来编译 OpenHarmony 侧的应用包,同一个 Dart 代码库里通过条件编译区分平台。
这里要提醒一句:OpenHarmony 的 Flutter 支持目前还处于“能用但别求全覆盖”的阶段。第三方插件大部分都要自己适配,社区里现成的 OpenHarmony 版插件数量远不如 Android/iOS。所以选型时要做的一个关键决策是:把 OpenHarmony 作为辅助发布平台,而不是主平台。功能上先保证核心链路(浏览、聊天、上传)可用,边缘功能砍一砍,这样既吃到生态红利,又不至于被平台适配拖住整个项目的进度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 接稿平台功能拆解:核心模块的设计与实现
2.1 作品画廊与图片加载方案
接稿平台的首页是画廊,承载着“展示画师水平、吸引甲方约稿”的核心任务。这里不是简单放一个 GridView 就完事了,图片加载策略直接决定用户体验和流量成本。画师上传的作品通常是几 MB 的高清原图,如果画廊里直接加载原图,不仅页面卡顿,流量也扛不住。所以必须做分级加载:缩略图、预览图、原图三级。
Flutter 生态里比较成熟的方案是 cached_network_image 配合自建缩略图服务。列表页加载 300px 左右的缩略图,详情页加载 1280px 的预览图,只有点击“查看大图”或者下载时才拉取原图。缓存策略上,缩略图和预览图走本地磁盘缓存,设置过期时间,避免版本更新后缓存文件堆积。这里有个细节:画师作品很多是有版权的,不允许随意下载,所以“禁止下载”的作品要绕开系统相册,只做内存预览。
图片加载这块还有一个容易被忽略的点——色彩空间。画师上传的图大部分是 sRGB,但也有一些资深画师会导出 Display P3 色域的图。Flutter 的 Image 组件默认不做色彩管理,导致部分 P3 图在普通屏幕上颜色发灰。这个我目前是用 ColorFiltered 加一个轻微的饱和度补偿来处理,虽然不如原生色彩管理严谨,但至少能让视觉差异控制在可接受范围内。
2.2 需求大厅与富文本渲染
接稿平台不仅是画师展示作品的地方,甲方也会在上面发布需求:“需要一张古风封面,预算 500,两周内交稿”。这类需求描述的格式很自由,甲方可能用纯文本,也可能复制一段带排版的长文,所以需求详情页必须要支持富文本渲染。这就是我之前被问得比较多的 flutter 渲染 HTML 富文本的问题。
Flutter 官方其实没有内置的富文本 HTML 渲染器,社区里常用的是 flutter_html 或者 flutter_widget_from_html。我在项目里用的是 flutter_widget_from_html,因为它对视频、图片、超链接的扩展支持更灵活。实现逻辑很简单:后端接口返回 HTML 字符串,前端用 HtmlWidget 直接渲染。
但这块有个性能大坑——如果 HTML 里嵌了外链图片,HtmlWidget 默认是直接按原始尺寸加载的,一个 5000px 宽的站外图片能直接把页面撑爆。解决办法是自定义 HtmlWidget 的 customStylesBuilder,限制所有图片最大宽度为屏幕宽度的 90%,并且强制 img 标签的 width 和 height 自动调整比例。还有一个常见问题:HTML 里的 <script> 标签会被忽略,但 <style> 里的 body 样式会影响全局,需要在渲染前做一层 sanitize,把 body 级样式改成容器级。
2.3 即时私信与底部弹窗输入框
画师和甲方沟通需求是高频操作,私信模块不能像留言板那么简陋。消息列表、聊天详情、图片消息、报价卡片、稿件缩略图……这些都是常规功能。真正让我折腾最久的是聊天输入框的键盘适配问题,这也是 Flutter 开发者里最高频的搜索词之一:“flutter 底部弹窗内有 text field”。
场景是这样的:用户点击聊天页底部的输入框,系统键盘弹出来,但如果你用了自定义的 showModalBottomSheet 来实现输入面板,你会发现键盘弹起时底部弹窗的位置和高度很难控制,有时键盘把弹窗整个顶飞,有时弹窗遮住了键盘顶部的“发送”按钮。
我最终采用的方案是:不用 showModalBottomSheet,改成用 Stack + AnimatedPadding 自己做一个底部输入区域。核心思路是监听 MediaQuery.of(context).viewInsets.bottom,当键盘弹出时,给底部栏加一个跟键盘高度一致的 padding。
dart复制return Scaffold(
resizeToAvoidBottomInset: true,
body: Stack(
children: [
Positioned.fill(child: MessageListView()),
Positioned(
left: 0, right: 0, bottom: 0,
child: AnimatedPadding(
duration: const Duration(milliseconds: 150),
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: ChatInputBar(),
),
),
],
),
);
这里有个比较隐蔽的坑:Scaffold 的 resizeToAvoidBottomInset 如果设为 true,整个 body 会在键盘弹出时被压缩,我们监听 viewInsets 又要给它加 padding,两者叠在一起会导致输入框被顶两次。正确的做法是设置 resizeToAvoidBottomInset: false,完全由自己控制输入条的位置。这样键盘弹出时消息列表不会被压缩,输入条始终稳稳地停在键盘上方。
2.4 订单与稿件交付管理
接稿平台跟普通社交平台最大的区别在于:它有明确的钱货交易闭环。订单状态流的梳理很重要:待接单 → 沟通中 → 已下单 → 绘画中 → 已交初稿 → 修改中 → 已交终稿 → 已验收 → 已打款。
订单状态机如果用 Flutter 状态管理去写,很容易被各种状态分支绕晕。我推荐的做法是把状态流转逻辑全部放到一个独立的 ChangeNotifier 里,UI 层只做状态映射。
dart复制enum OrderStatus {
pending, // 待接单
negotiating, // 沟通中
paid, // 已下单
drawing, // 绘画中
delivered, // 已交初稿
revising, // 修改中
finalDelivered, // 已交终稿
completed, // 已验收
cancelled, // 已取消
}
每个状态变更都触发服务端的 webhook 通知,画师在 App 内收到推送:“订单 #1024 已打款,款项已到账”。推送这块用的是极光推送,不过 OpenHarmony 端目前还没有现成的推送插件,只能退化成 App 内轮询,这个等后续官方推送能力完善了再补。
3. OpenHarmony 端适配:从编译到上板的完整流程
3.1 开发环境与设备准备
OpenHarmony 开发跟通用 Android 开发的思路类似,但工具链差别不小。首先,你要有一个标准系统的 OpenHarmony 设备,常见的选择是润和、触觉智能这些厂商的 RK3566/RK3568/RK3588 开发板,或者直接用开源鸿蒙手机。热搜里常出现 openharmony rk3568、openharmony rk3588,说明这俩是大多数人手头的调试主力。RK3588 的性能强不少,跑 Flutter 应用流畅度明显更高,预算允许的话建议直接上它。
连通设备和电脑,用的是 OpenHarmony 自己的调试工具 hdc,类似 Android 的 adb。连上设备后第一件事就是确认系统版本:
bash复制hdc shell param get const.product.name
hdc shell param get const.product.software.version
hdc shell param get const.product.devicetype
这就是热搜里 hdc 查看 openharmony 系统版本 param get 的来源。这几个参数能帮你确认设备型号、软件版本和设备类型,避免拿一个 API 版本不匹配的系统去跑 Flutter 应用,编译出来的 hap 包装了却跑不起来。
构建 Flutter 应用到 OpenHarmony,目前是通过社区维护的 OpenHarmony 版 Flutter SDK 来完成的。你在项目里执行 flutter build hap --release,它会调用对应平台的编译链,最终产出 .hap 安装包。注意这个命令只在 OpenHarmony 版的 flutter_flutter 分支中存在,原版 Flutter SDK 里是没有 hap 这个目标的。
3.2 真机调试与设备信息那些事
开发 OpenHarmony Flutter 应用,第一阶段你大概率会在模拟器或开发板上跑。但真机调试时有个很烦的问题:在 OpenHarmony 上获取设备唯一标识的坑。Android 那边用 Settings.Secure.ANDROID_ID,OpenHarmony 这边不一样,推荐用 deviceManager.getUdid() 或者命令行工具:
bash复制hdc shell bm get -u
这个命令会拿到设备的 UDID(Unique Device Identifier)。对于接稿平台来说,UDID 主要用于设备登录态绑定和安全风控,就像电商 App 用 IMEI 一样。但注意别把它存到明文本地文件里,OpenHarmony 的文件系统开放程度不如 Android,一些目录的访问权限跟 Android 不是一套逻辑。
热搜里还有个词是 openharmony devudid 和 serial。UDID 是更长更唯一的设备标识,serial 是序列号,两者不要搞混。如果你的业务需要上报设备信息做多端登录管理,建议只上报 UDID,因为它跟 Google Play Services 那套逻辑类似,卸载重装也不变。
3.3 Flutter 调用 OpenHarmony 能力:平台通道
跨端应用最核心的问题是:Flutter 层跑通了 UI,但怎么调用 OpenHarmony 的系统能力?比如打开系统文件选择器、申请通知权限、读取设备型号等。答案还是 MethodChannel,只是 OpenHarmony 端的实现方式跟 Android/iOS 不一样。
OpenHarmony 上 Flutter 插件工程的开发语言是 ArkTS,这也是 OpenHarmony 应用的主力开发语言。以获取设备型号为例,Flutter 侧这样写:
dart复制static const platformChannel = MethodChannel('com.artistplatform.ohos/device');
final String model = await platformChannel.invokeMethod('getDeviceModel');
OpenHarmony 侧在插件工程里注册对应 channel 并实现方法。这里要特别提醒:OpenHarmony 的权限模型和 Android 很不一样,很多 API 调用前需要先在 module.json5 里声明权限,而不是像 Android 那样在运行时动态申请。比如读取设备信息,你得先加 ohos.permission.GET_NETWORK_INFO 之类的权限声明。这个细节如果没提前看文档,调试时会卡很久。
3.4 手绘板、USB 外设与本地文件下载
画师平台还有一个比较进阶的场景:接稿画师有时会用电脑端 Flutter 应用连接数位板/手绘屏做草稿预览。OpenHarmony 开发板通常带 USB Host 接口,这就涉及到底层 USB 通信。热搜里的 openharmony usbmanager libusb 的使用 其实就是干这个的。在 OpenHarmony 上,@ohos.usbManager 提供了 USB 设备枚举和控制传输接口,你可以用它读取数位板的输入数据,不过这套 API 目前还在演进中,稳定性一般,我目前只是在实验性功能里用,不建议把它放进主链路。
还有一个很务实的亮点:flutter 下载文件到私有目录,不需要文件权限。这个是 Flutter 在 OpenHarmony 上做得比较好的一点。接稿平台经常需要下载水印预览图、合同文件,在 Android 上你得动态申请存储权限,在 OpenHarmony 上,如果你的应用只把文件写进自己的私有沙箱目录(/data/storage/el2/base/),是不需要申请任何存储权限的。这让“免权限下载”成为可能,对画师这种高频收发文件的场景很友好。代码写法上跟 Android 差不多,用 path_provider 拿到 App 文档目录即可,OpenHarmony 版也提供了对应的 path_provider 实现。
dart复制final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/preview_watermarked.jpg');
await file.writeAsBytes(bytes);
4. 构建打包与那些年我踩过的报错
4.1 Android 端打包 APK 与 Gradle 经典报错
虽然主打 Flutter × OpenHarmony,但平台的第一个正式版本还是要发 Android,因为 OpenHarmony 设备存量还不够大。打包 APK 的过程本身不复杂,flutter build apk --release 一条命令搞定。但这条命令背后是 Gradle 在使用 Flutter 插件时踩坑的高发区。
第一个高频报错是:
text复制You are applying Flutter's main Gradle plugin imperatively using the apply script method...
这个报错的本质是 Flutter Gradle 插件与新版 Gradle 的兼容性问题。Flutter 上游在某个版本之后把 Gradle 插件从“脚本方式应用”切换成了“pluginManagement 方式应用”,如果你用的 Flutter 版本比较老,但项目里开了新版的 Gradle wrapper,就会触发这个断言。解决办法很简单:升级 Flutter SDK 到最新稳定版,然后按提示把项目根 settings.gradle 改成 pluginManagement 引入的方式。
第二个高频报错是:
text复制flutter error resolving plugin [id: 'dev.flutter.flutter-plugin-loader', version: ...]
这个其实是网络问题。Gradle 要从 Maven 仓库拉取 Flutter 插件,但某些网络环境下仓库访问不稳定。排查思路是:先确认本机能不能正常访问 maven.google.com 和 repo.maven.apache.org,再检查项目的 build.gradle 里仓库地址是否写全。国内环境还可以在项目级 gradle.properties 里配置镜像仓库。
第三个更隐性的坑是:
text复制flutter assets will be downloaded from https://storage.flutter-io.cn. make sure ...
这是国内镜像指示。很多人以为这行只是日志,其实它说明你的 Flutter SDK 配置了国内镜像源。对,配置了镜像就好,万一你在某些环境里没配镜像,下载引擎库会特别慢。建议在系统环境变量里显式设置:
bash复制export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
4.2 OpenHarmony 编译产物清理与构建优化
OpenHarmony 版 Flutter 工程编译完,会在项目里生成一堆中间产物,体积大且占磁盘。很多人会问:openharmony 编译出来的文件哪些可以删除。我实际用下来,以下这些目录都是可以安全清理的,下次构建自动生成:
build/:临时构建产物.hvigor/:构建缓存oh_modules/:OpenHarmony 侧的三方依赖entry/build/:hap 包输出目录(但如果你要保留安装包,记得先拷贝出来)
但注意,AppScope/ 和 entry/src/ 这两个目录别动,它们是 OpenHarmony 应用的源码目录,删了就得重新建工程。
构建优化方面有一个小技巧:OpenHarmony 的 Flutter 构建链路是“Dart 编译 → ArkTS 编译 → 打包”。如果只是改了 Dart 代码,可以先用 flutter build hap --debug 跑通,再做 --release 的完整构建。Debug 模式的构建速度会快很多,适合频繁调试 UI 的阶段。
4.3 Flutter 更新后导致的编译异常
这是一个让我头大的问题:某次我把 Flutter SDK 从 3.16 升到 3.24 之后,项目编译直接报 java.lang.AssertionError: java.lang.Exception: could not parse json 一类的异常。这个报错信息没有明确的错误位置,排查起来非常难受。
最后定位到的问题是:Flutter 升级后,pubspec.lock 文件里某些旧版本依赖的解析规则不再兼容,flutter pub get 拉下来的包版本与新版 SDK 冲突。解决办法是删掉 pubspec.lock,重新执行 flutter pub get,让新版 SDK 重新解析依赖树。
这个坑提醒我:Flutter 升级不要跳版本,每升一个大版本都要回归测试一遍。升级前先把当前 pubspec.lock 备份好,万一翻车可以快速回滚。
4.4 视频渲染与解码报错
平台后续加了“过程直播”功能,就是画师头屏录制绘画过程,甲方可以实时观看。这个功能在 iOS 和 Android 上没问题,但 OpenHarmony 端跑起来后,视频播放经常黑屏,日志里弹出来:
text复制flutter mediacodecvideorenderer error
这是 Flutter 在 OpenHarmony 上使用 MediaCodec 视频硬解时常见的兼容性问题。OpenHarmony 的媒体编解码框架与 Android 的 MediaCodec 有差异,某些 H.264/HEVC 编码参数直接不认。我绕开的方式是:在 OpenHarmony 端禁用视频硬件解码,强制走软件解码。Flutter 的视频播放插件一般都有对应的 usePlatformView 或 enableHardwareAcceleration 开关,把这个开关关掉,画质损失一点,但至少能出画面。
后来我查了下社区的讨论,这问题本质是 OpenHarmony 的视频编解码 HAL 层还在完善,部分 RK 平台的固件对 1080p 以上的视频流支持不稳定。如果你们团队有硬件驱动的同学,可以自己修 HAL 层;没有的话,软件解码是最省事的兜底方案。
5. 状态管理、生命周期与项目维护心得
5.1 Provider 还是 Riverpod:接稿平台的选择
接稿平台的状态管理,我最终用的是 Provider 而不是 Riverpod。原因很实际:项目里接的第三方服务(微信登录、支付、推送)大多有现成的 Provider 封装,团队后续接手的同学也更容易上手。热搜里有个 flutter provider插件使用教程,说明这块需求确实旺盛。Flutter 官方现在的推荐里已经逐渐偏向 Riverpod 了,但我不建议为了追新而追新,项目里超过五个人协作时,选择“团队最熟悉且生态最全”的方案远比“语法最先进”的方案更靠谱。
Provider 的用法其实很简单,核心是三个对象:ChangeNotifier(状态载体)、ChangeNotifierProvider(注入顶层)、Consumer 或 context.watch(监听刷新)。我在订单详情页就是用一个 OrderDetailViewModel extends ChangeNotifier 管理订单状态机,页面组件拆成三个 Consumer,分别监听基础信息、进度条、操作按钮,避免任何状态变更都重建整个页面。
5.2 Flutter 生命周期在接稿场景中的实战
Flutter 的生命周期是面试高频题,也是接稿平台里实实在在要打交道的核心点。画师画图时切后台了几分钟,回到 App 时如果草稿丢了,体验是非常糟糕的。我在 App 的 WidgetsBindingObserver 里监听了应用生命周期:
inactive:App 进入失活状态,此时把正在编辑的需求文本自动保存到本地paused:进入后台,触发消息数据的静默同步
dart复制class AppLifecycleObserver with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.inactive:
DraftRepository.instance.saveCurrentDraft();
break;
case AppLifecycleState.paused:
MessageSyncService.instance.syncPendingMessages();
break;
default:
break;
}
}
}
这样画师切出去翻资料、看参考图,回来之后草稿还在,聊天消息也同步到最新状态了。
还有一个生命周期细节:在 OpenHarmony 设备上,应用进入后台后进程有概率被杀掉。Flutter 在 OpenHarmony 上目前只能做一个前台的 UI 框架,没有完善的后台任务保活机制,所以“消息推送”这块只能靠服务端在用户下次打开 App 时拉取增量,没法像 iOS 那样走 APNs。这个限制要在产品设计时就考虑进去,别到上线了才知道消息收不到。
5.3 flutter 内嵌 uniapp 小程序值不值
热搜里还有一个词是 flutter内嵌uniapp 小程序。有人会想:接稿平台里要不要内嵌一个第三方开发者的生态,让画师可以运行别人的小程序?技术上可行,但我真不建议。Flutter 本身就是一个高性能 UI 引擎,再套一个小程序运行时(比如 uni-app 的 mini-program runtime)进去,包体积直接增加 30MB 起步,性能也会因为多一层解释器打折扣。接稿平台的核心诉求是“快和稳”,不是“生态扩展”。如果你有轻量功能要内置,直接用 Flutter 写过滤组件就好,没必要套壳。
5.4 跨端 UI 一致性的隐形成本
Flutter 的跨端一致性是一个巨大卖点,但“一致”不等于“零成本”。很多时候,你会为了在不同平台跑出完全一致的 UI,额外写很多平台差异代码。比如在 OpenHarmony 和 Android 上,系统字体的渲染引擎不同,同一字号显示出来的视觉大小会有偏差。解决方式是在基础组件层用 MediaQuery.textScaler 做一次统一缩放,而不是每页去调字号。
还有一个容易被忽的坑:不同平台的 SafeArea 逻辑不一样。iPhone 有刘海和底部手势条,Android 常见挖孔屏,OpenHarmony 开发板通常是 16:9 的电视屏幕,没有刘海但可能有系统导航栏。SafeArea 组件需要结合各平台的 MediaQuery.padding 来设置,否则在某个平台上就会被系统 UI 遮挡。
我封装了一个 AdaptivePadding 组件,内部根据 Platform.isAndroid || Platform.isIOS || Platform.isOpenHarmony 动态计算 safe area 偏移。
dart复制EdgeInsets get adaptivePadding {
if (Platform.isOpenHarmony) {
return EdgeInsets.only(top: MediaQuery.of(context).padding.top + 8);
}
return MediaQuery.of(context).padding;
}
这样写起来有点啰嗦,但总比每个页面都堆条件判断要干净得多。
个人实操中的最后几点建议
做这个平台的过程中,我最大的体会是:跨端不是终点,稳定才是核心。Flutter 让一套代码跑南北,OpenHarmony 让应用多了一个潜在的设备增量入口,但用户选择画师平台,看的不是技术栈有多炫,而是界面是否流畅、图片加载是否够快、聊天消息是否可靠送达。所以我的原则一直是:能省的原生适配就省,不能省的细节一定各平台亲测。比如 OpenHarmony 上字体渲染、键盘弹出、系统返回手势这些,每换一个平台都要重新过一遍。
如果你也想做类似方向,给你一个实用的建议:把 OpenHarmony 当作“第二发布渠道”而不是“主力平台”,前期先把 Android/iOS 打磨好,积累真实用户反馈,再花时间适配 OpenHarmony 的核心功能链路,而不是一上来就全端开花。Flutter 的优势是让你并行,不是逼你并行。项目跑起来了,随着 OpenHarmony 设备量和 Flutter 适配进一步完善,那个版本的增量收益会自然落到你头上。
