1. 为什么是"Flutter + 鸿蒙"这个组合
1.1 物业通知类APP的真实痛点
先交代一下项目背景。小区物业每天都有大量通知要发:停水停电、电梯检修、物业费催缴、社区活动、快递驿站变更……过去的方式无非是楼道贴告示、业主群里刷屏、打电话通知。楼道贴告示容易被撕掉,业主微信群消息太多容易被淹没,电话通知又费人力。做一个小区物业通知APP,把通知统一聚合起来,其实是个很实际的需求。
但真正动手做的时候,问题来了:物业公司没有多少研发预算,小区业主手里的手机品牌五花八门,有安卓、有iPhone,现在还有不少人是鸿蒙系统的手机。如果按传统思路给安卓做一套、给iOS做一套、再给鸿蒙做一套,三套原生代码的开发和维护成本,物业公司根本承受不起。这时候跨平台方案就成了比较务实的选择。
1.2 Flutter在鸿蒙生态里的位置
这里要先说清楚一个技术现状:Flutter官方主线对鸿蒙的适配,目前主要靠社区和生态来推动。实际工程中我们用的是专为OpenHarmony/HarmonyOS NEXT适配的Flutter分支,配合DevEco Studio和鸿蒙SDK,可以做到一套Flutter代码跑通安卓、iOS、鸿蒙三个平台。这才是"Flutter + 鸿蒙跨平台"这个组合的真正含义——它并不是谷歌官方直接支持鸿蒙,而是通过鸿蒙生态的适配分支,把Flutter的渲染引擎和Dart运行时跑在鸿蒙设备上。
用更通俗的话讲,Flutter做的事情很像"装修队统一施工":不管房子用的是安卓的砖、iOS的砖还是鸿蒙的砖,Flutter都在上面铺一层自己的地板,所有家具都放在这层地板上,不用为每栋房子单独设计一套家具。鸿蒙适配分支做的工作,就是把"这层地板"铺设到鸿蒙的房屋结构上,让它能正常承重、走水电。
1.3 对比纯ArkTS开发和其他跨平台方案
你可能想问:既然鸿蒙有自己的原生开发语言ArkTS,为什么不直接用ArkTS开发?这里要做个客观对比。
| 方案 | 优势 | 劣势 | 适合场景 |
|---|---|---|---|
| 纯ArkTS开发 | 对鸿蒙特性支持最完整,系统能力调用最直接 | 只覆盖鸿蒙,无法同时覆盖安卓和iOS | 鸿蒙独占应用、需要深度系统集成的应用 |
| uni-app | 上手快,生态成熟,一套代码多端发布 | 性能和复杂交互不如Flutter流畅,鸿蒙适配依赖各家HBuilder版本 | 中小型应用、快速上线 |
| Flutter(鸿蒙适配分支) | 渲染性能强,UI一致性好,动画流畅,适合列表密集类应用 | 鸿蒙适配依赖社区分支,部分插件需要自己适配 | 追求跨端一致体验、UI交互复杂的应用 |
物业通知APP的特点是:列表页多、详情页多、需要滚动加载、需要富文本展示,这些恰好是Flutter的强项。另外,Flutter的UI渲染是自绘引擎,不依赖系统组件,这意味着在安卓、iOS、鸿蒙三个平台上展示效果几乎一模一样——对业主来说,不管用什么手机,看到的小区通知界面是一样的,这个体验一致性在日常运营中非常加分。
于是项目定了:Flutter做客户端框架,鸿蒙适配分支做鸿蒙端支持,业务功能按跨平台思路开发。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 鸿蒙适配环境的搭建与配置
2.1 工具链清单与版本匹配
做鸿蒙适配,工具链和纯Flutter开发差别不小。我这边的环境清单是这样的:
| 工具 | 版本选择 | 说明 |
|---|---|---|
| Flutter SDK | OpenHarmony 社区适配分支(需拉取ohos相关分支) | 不是谷歌官方主线,别用错 |
| Dart SDK | 随Flutter分支自带 | 无需单独安装 |
| DevEco Studio | 5.0以上版本 | 鸿蒙官方IDE,用于创建鸿蒙工程和HAP打包 |
| OpenHarmony SDK | 与目标设备系统版本匹配(API 9及以上) | 在DevEco Studio中通过SDK Manager安装 |
| hdc工具 | 随DevEco Studio附带 | 相当于安卓的adb,用于连接鸿蒙真机 |
这个版本匹配是个大坑。我最初用Flutter 3.19的官方主线尝试直接构建鸿蒙包,结果四处碰壁——插件加载失败、Gradle编译报错。后来才搞清楚,必须使用鸿蒙生态维护的Flutter适配分支,这个分支在官方Flutter版本基础上增加了ohos平台的构建支持和运行时适配。
正确的安装路径是:拉取适配分支的代码并切换分支,然后通过 flutter config --enable-ohos 开启鸿蒙平台支持。开启之后,用 flutter doctor 检查环境,会看到Ohos工具链的检测项。这一步通过之后,才算是真正的"Flutter能感知到鸿蒙设备"。
2.2 启用ohos平台的完整步骤
环境装好之后,在已有Flutter工程里启用鸿蒙平台,我记录了完整的操作顺序:
bash复制# 1. 拉取鸿蒙适配分支的Flutter SDK,并切换对应分支
git clone https://gitee.com/openharmony-sig/flutter_flutter.git
cd flutter_flutter
git checkout openharmony-5.0
# 2. 把Flutter命令指向这个SDK(注意替换成你自己的路径)
export PATH="$PATH:/path/to/flutter_flutter/bin"
# 3. 验证当前Flutter版本与鸿蒙支持状态
flutter --version
flutter config --enable-ohos
# 4. 在已有的Flutter工程目录下追加ohos平台目录
flutter create --platforms ohos .
# 5. 构建鸿蒙调试包
flutter build hap --debug
跑完第4步后,工程里会多出一个 ohos 目录,这个目录就是鸿蒙原生工程外壳。flutter build hap 会把它和Flutter的产物一起打包成鸿蒙应用包。
我在这个过程中遇到的第一个报错是:
text复制[ERROR] Unknown project type ohos. To see the complete set of supported platform types, run "flutter config --list".
这个报错说明你的Flutter SDK并不是包含ohos支持的适配分支——回到第1步重新检查分支是否切对。另一个常见现象是DevEco Studio打开 ohos 目录后提示SDK版本不匹配,建议在DevEco Studio里逐个尝试可用的SDK版本,选一个和适配分支约定一致的API版本。
2.3 写在配置前的几个注意事项
这里整理几个最容易让人反复折腾的细节,提前说明省得你走弯路。
- 严格区分"手机鸿蒙"和"开源鸿蒙":我们在开发时主要针对商用手机HarmonyOS NEXT和部分搭载开源鸿蒙的开发板。两者的SDK版本、API能力存在差异。如果目标只是跑通手机设备,直接使用DevEco Studio自带的HarmonyOS SDK即可。
- 不要直接用官方Flutter命令升级依赖:因为适配分支有自己的版本节奏,
flutter upgrade可能会把你带回官方主线,导致ohos支持失效。建议用git pull拉取最新提交,而不是执行flutter upgrade。 - 权限声明和安卓不一样:鸿蒙的网络权限、通知权限、存储权限不在AndroidManifest里,而在
ohos目录下的module.json5中配置。比如访问网络要加:
json5复制{
"module": {
"name": "entry",
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "用于获取物业通知数据",
"usedScene": {
"abilities": ["EntryAbility"]
}
}
]
}
}
如果你是从安卓转过来的,很容易漏掉这一步,然后真机调试时发现网络请求全部失败,还找不到原因——因为Flutter端的Dio根本不会报权限错误,只是请求被系统拦掉。
3. 物业通知APP的核心功能拆解与实现
3.1 业务功能全景
物业通知APP分成用户端和管理端两个角色。用户端面向业主,核心功能是查看通知;管理端面向物业运营人员,核心功能是发布通知和统计数据。
用户端的功能模块:
| 功能模块 | 具体内容 | 实现要点 |
|---|---|---|
| 通知列表 | 按类型分Tab展示:全部、公告、缴费、报修、活动 | 列表滚动加载、未读标记、下拉刷新 |
| 通知详情 | 富文本内容展示、发布时间、发布人、附件下载 | 支持HTML富文本,或Markdown渲染 |
| 房间绑定 | 业主输入楼栋-单元-房号完成身份绑定 | 本地存储+服务端校验 |
| 消息推送 | 新通知到达时通过通知栏提醒 | 本地通知+服务端触发 |
| 我的页面 | 设置项、阅读历史、消息提醒开关 | 简单的列表页 |
管理端的功能模块:
| 功能模块 | 具体内容 | 实现要点 |
|---|---|---|
| 发布通知 | 填写标题、正文、类型,选择目标楼栋 | 表单校验+富文本编辑器 |
| 定时发布 | 设定发布时间到点自动发出 | 本地定时通知+服务端定时任务 |
| 阅读统计 | 查看通知的已读/未读情况 | 列表+图表展示 |
这篇主要讲用户端的开发流程,管理端涉及后台服务的部分点到为止,重点放在Flutter端的技术实现上。
3.2 数据模型与本地存储设计
通知列表是核心页面,数据模型设计成什么样,直接决定后面代码好不好写。我最后定下的模型是这样:
dart复制enum NoticeType { all, announcement, payment, repair, activity }
class Notice {
final String id;
final String title;
final String content;
final NoticeType type;
final DateTime publishTime;
final String publisher;
final List<String> targetBuildings; // 目标楼栋列表,空表示全部
final bool isRead;
final bool isTop;
Notice({
required this.id,
required this.title,
required this.content,
required this.type,
required this.publishTime,
required this.publisher,
this.targetBuildings = const [],
this.isRead = false,
this.isTop = false,
});
factory Notice.fromJson(Map<String, dynamic> json) {
return Notice(
id: json['id'] as String,
title: json['title'] as String,
content: json['content'] as String,
type: NoticeType.values.firstWhere(
(e) => e.name == json['type'],
orElse: () => NoticeType.announcement,
),
publishTime: DateTime.parse(json['publishTime'] as String),
publisher: json['publisher'] as String,
targetBuildings: (json['targetBuildings'] as List<dynamic>? ?? [])
.map((e) => e as String)
.toList(),
isRead: json['isRead'] as bool? ?? false,
isTop: json['isTop'] as bool? ?? false,
);
}
}
本地存储我用了Hive而不是shared_preferences。原因是通知数据是结构化的,而且量可能比较大,Hive支持存自定义对象列表,读取速度快,还支持增量写入。这个项目的数据量不大,但用户会频繁打开通知列表,用Hive在本地缓存一份历史通知,即便网络不可用也能让业主看到已经加载过的通知内容。
Hive的初始化要放在 main() 里尽早完成:
dart复制void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(NoticeAdapter());
// 判断是否已打开过通知存储
var box = await Hive.openBox<Notice>('notice_cache');
runApp(MyApp(noticeBox: box));
}
3.3 通知列表页的实现细节
列表页其实没有太多花哨的东西,但有几个细节值得说说。我用了 NestedScrollView 配合 TabBar 实现"滑到顶部的分类Tab吸顶"效果,顶部再加一个搜索框,方便业主在通知很多时快速筛选。
核心结构伪代码:
dart复制DefaultTabController(
length: 5,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
title: const Text('小区通知'),
pinned: true,
bottom: const TabBar(
tabs: [
Tab(text: '全部'),
Tab(text: '公告'),
Tab(text: '缴费'),
Tab(text: '报修'),
Tab(text: '活动'),
],
),
),
],
body: const TabBarView(
children: [
NoticeList(type: NoticeType.all),
NoticeList(type: NoticeType.announcement),
NoticeList(type: NoticeType.payment),
NoticeList(type: NoticeType.repair),
NoticeList(type: NoticeType.activity),
],
),
),
),
)
每个 NoticeList 内部用 ListView.builder 做懒加载,滚动加载通过 ScrollController 监听达到底部时触发下一页请求:
dart复制void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
未读状态用一个小红点表示,用 Badge 组件包在标题后面,不再额外做复杂的红点逻辑。列表项左侧放一个类型图标,用不同颜色区分通知类型。
3.4 通知详情页与富文本展示
详情页是用户阅读通知的主要入口。这里有一个技术选择要做:通知内容可能是物业编辑的带格式文本,比如加粗、换行、插入图片。怎么渲染?
我试过两种方案,简单对比一下:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| flutter_html 渲染HTML | 支持标签丰富,物业后台可直接用富文本编辑器 | 部分CSS样式不支持,偶尔有解析错位 | 后台已有HTML富文本编辑器 |
| Markdown渲染(flutter_markdown) | 语法简单,样式统一 | 物业人员需要学习Markdown语法,门槛稍高 | 后台编辑人员技术水平较好 |
最终选了flutter_html,因为大多数物业后台的编辑人员习惯使用类似Word的富文本编辑器,导出的内容天然是HTML格式,没必要让他们重新学Markdown。flutter_html在鸿蒙端的适配也踩了个小坑:加载网络图片时需要额外处理图片路径的形式,鸿蒙沙箱路径和安卓不完全一致,直接用相对路径会加载失败。解决方法是把图片URL补全为完整地址后再交给flutter_html渲染。
dart复制Html(
data: notice.content,
onLinkTap: (url, attributes, element) {
// 打开外链前做URL合法性校验
if (url != null && url.startsWith('https://')) {
launchUrl(Uri.parse(url));
}
},
)
4. 通知推送与App生命周期管理
4.1 推送方案选型
物业通知APP的推送需求有一个特点:频率不高,但每条通知都很重要。不需要像新闻资讯那样追求秒级到达海量下发,但必须保证业主在通知发布后的合理时间内能收到提醒。
我调研了三条路线:
- 厂商推送(华为PUSH Kit / 小米推送 / 个推):到达率好,但每个厂商都要单独申请,代码里要多套SDK,接入量级对一个物业类应用来说偏重。
- 自建长连接(WebSocket / MQTT):可控性强,一套代码跨全平台,但需要自己维护服务端连接层。
- 本地轮询 + 本地通知:实现最简单,App定期拉取服务端新增通知,发现新通知就在本地弹通知栏提醒。
权衡之后,我选了"服务端WebSocket推送 + 本地通知兜底"的混合方案:服务端有新通知时通过WebSocket推给在线客户端,客户端收到后写本地缓存并弹出通知栏;如果WebSocket断开(比如进程被系统回收),下次打开App时会通过列表刷新拉取遗漏的通知,再根据时间戳补发本地通知。这样既保证了实时性,又不用承担厂商推送SDK的接入成本。
4.2 前后台切换与消息到达的处理
Flutter开发里,前后台切换的处理绕不开生命周期观察。鸿蒙系统的App生命周期模型和安卓有差异,但Flutter抽象了一层,所以Flutter代码里的生命周期回调仍然可以复用到鸿蒙端。
我在 main 里注册了一个全局生命周期观察器:
dart复制class AppLifecycleObserver with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
// 回到前台:立即刷新通知列表,检查是否有遗漏通知
NoticeRefreshController.instance.refreshImmediately();
break;
case AppLifecycleState.paused:
// 进入后台:保存当前未读状态、暂停不必要的动画
break;
case AppLifecycleState.inactive:
// 失活状态,比如来电话、打开控制中心
break;
case AppLifecycleState.detached:
// 引擎分离,进程可见但界面不可交互,做资源释放
break;
case AppLifecycleState.hidden:
// 鸿蒙/安卓等平台特有的隐藏状态
break;
}
}
}
注意 hidden 状态是Flutter 3.13之后新增的枚举值,对应的是界面完全不可见的场景。如果不处理这个状态,可能在鸿蒙设备上出现"通知已经弹出来了,但App还在后台收到推送时重复弹通知"的问题——因为App没有感知到自己已经处于hidden状态,仍然执行了前台才该执行的推送逻辑。
4.3 本地通知的定时触发
物业经常有"今晚8点停水,提前通知业主"这种需求。在App端,如果服务端只是把停水通知发过来,客户端可以根据通知里的 effectiveTime 字段做本地定时提醒。
这里用到了 flutter_local_notifications 的 zonedSchedule 定时任务能力:
dart复制Future<void> scheduleNoticeReminder(Notice notice) async {
final tzLocation = tz.getLocation('Asia/Shanghai');
final scheduleTime = tz.TZDateTime.from(notice.effectiveTime, tzLocation);
await flutterLocalNotificationsPlugin.zonedSchedule(
notice.id.hashCode,
notice.title,
'预计在 ${_formatTime(notice.effectiveTime)} 开始,请提前准备',
scheduleTime,
const NotificationDetails(
android: AndroidNotificationDetails(
'notice_channel',
'物业通知',
channelDescription: '小区物业通知提醒',
importance: Importance.high,
priority: Priority.high,
),
// 鸿蒙端部分参数与安卓不同,需要单独适配
),
androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle,
);
}
需要特别提醒的是,flutter_local_notifications 这个插件在鸿蒙适配分支里可能没有现成的原生实现。遇到这种情况有三个处理方向:
- 看插件的pubspec里是否声明了
ohos平台的原生实现; - 没有的话去OpenHarmony生态的组件仓库找鸿蒙适配版,确实有社区维护的移植版本;
- 实在没有,只能自己用Platform Channel写一层鸿蒙原生通知API的对接。
我这次运气比较好,找到了一个社区维护的鸿蒙适配版本,直接替换依赖即可:
yaml复制dependencies:
flutter_local_notifications:
git:
url: https://gitee.com/xxx/flutter_local_notifications_ohos.git
ref: ohos
4.4 定时任务的时区与系统权限坑
做定时通知时,最容易踩的是时区问题。zonedSchedule 要求传入时区信息,如果你传入的是设备本地时间但没明确指定时区,在跨时区旅行时会出问题。物业通知的场景虽然用户不会经常跨时区,但稳妥起见还是在代码里硬编码了Asia/Shanghai。
另一个问题是鸿蒙系统的通知权限。鸿蒙系统对通知权限的管理比较严格,用户可能在设置里关闭了某个App的通知权限,而Flutter端通过插件去查询权限状态时,鸿蒙适配版本的返回值和安卓不同。我在处理权限申请时做了兼容:
dart复制final plugin = flutterLocalNotificationsPlugin;
final androidImpl = plugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
final result = await androidImpl?.requestNotificationsPermission();
// 鸿蒙端的权限申请路径不同,需要走鸿蒙SDK自己的权限接口
if (!Platform.isOhos) {
// 安卓逻辑
} else {
// 鸿蒙逻辑:通过ohos.permission.NOTIFICATION_CONTROLLER申请
}
这个兼容写得很丑,但跨平台开发就是这样,平台差异是绕不开的。后续如果适配分支成熟了,这部分可以抽象成统一的封装。
5. 鸿蒙打包:从APK思维切换到HAP思维
5.1 HAP和APK的本质区别
做了多年Flutter安卓开发的人,首次碰鸿蒙打包时最不适应的就是"包"的概念变了。安卓最终产物是APK,鸿蒙最终分发的是HAP(HarmonyOS Ability Package)。这里不深入讲鸿蒙的组件模型,只说打包层面的直观差异:
- APK可以直接通过
flutter build apk产出,签名用keystore。 - HAP需要DevEco Studio配合签名配置,签名文件是p12证书和cer证书的体系,不再是你熟悉的
.jks或.keystore。 - HAP的构建会读
ohos目录下的build-profile.json5和module.json5,对应安卓的build.gradle和AndroidManifest.xml。
5.2 打包签名中容易踩的坑
我在打包签名阶段栽过几次跟头,整理出来给大家参考。
第一个坑是签名配置位置。安卓签名写在 android/app/build.gradle 里,鸿蒙的签名配置不在 module.json5,而是在 build-profile.json5 里,通过signingConfigs引用p12和cer的路径:
json5复制{
"app": {
"signingConfigs": [
{
"name": "default",
"type": "HarmonyOS",
"material": {
"certpath": "/path/to/xxx.cer",
"storePassword": "123456",
"keyAlias": "debugKey",
"keyPassword": "123456",
"profile": "/path/to/xxx.p7b",
"signAlg": "SHA256withECDSA",
"storeFile": "/path/to/xxx.p12",
"storePwd": "123456"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "default"
}
]
}
}
第二个坑是证书类型。鸿蒙的调试证书和发布证书是分开的,而且调试证书有设备限制——只能安装在白名单里的设备上。如果你的测试机换了一台,需要重新生成调试证书并加入设备白名单,否则 hdc install 直接报签名校验失败。网上搜到很多"安装失败"的错误,十有八九是证书和设备不匹配,不是代码问题。
第三个坑是 flutter build hap 的产物路径。构建成功后HAP的默认输出路径是:
text复制ohos/entry/build/default/outputs/default/entry-default-signed.hap
如果打开DevEco Studio构建,HAP可能输出在另一个路径。建议统一用命令行构建,保证CI脚本的可复现性。
5.3 真机安装与调试心得
真机安装用hdc工具,和adb的用法高度相似:
bash复制# 连接设备
hdc list targets
# 安装HAP
hdc install /path/to/entry-default-signed.hap
# 启动应用
hdc shell aa start -a EntryAbility -b com.example.notice
# 查看日志(筛选Flutter标签)
hdc hilog | grep flutter
调试阶段建议开启 flutter run --debug,这样Hot Reload在鸿蒙端也能用。不过说实话,鸿蒙端的Hot Reload没有安卓端那么丝滑,偶尔会有偶发性的崩溃,比如改了插件原生代码后热重载导致的状态错乱。稳妥做法是:只改Dart层代码时用Hot Reload,涉及原生依赖或插件的改动,直接冷重启。
6. 实测中的性能与适配优化
6.1 深色模式与字体适配
物业通知APP的用户年龄层分布较广,字体适配比一般App更重要。鸿蒙系统在字体大小调节方面做得比较灵活,Flutter端如果使用默认的Material 3组件,大部分情况下能跟随系统字体缩放,但固定高度的组件还是会出问题。
我在列表项里给标题和正文设置了明确的 maxLines 和 overflow 策略,防止大字体模式下文字溢出打乱布局。同时,通知列表的卡片高度不要写死,用最小高度加自适应:
dart复制Container(
constraints: const BoxConstraints(minHeight: 72),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
notice.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
notice.content,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
)
深色模式方面,Material 3默认支持跟随系统亮暗切换,但物业通知里大量使用类型图标颜色,这些自定义颜色在深色模式下容易对比度不足。我直接把每个类型的颜色值在Theme里定义好,用 Theme.of(context).brightness 判断后切换不同的色阶,避免全局搜索替换颜色的麻烦。
6.2 通知列表滑动的性能优化
Flutter性能优化的首要目标就是列表滚动流畅。物业通知列表虽然数据量不会特别大,但每一条通知包含富文本内容,如果直接在列表项中渲染完整内容,滚动帧率会明显下降。
我做的优化有三点:
第一,列表项只显示标题、摘要和时间,完整内容进入详情页再渲染。
第二,图片懒加载。通知列表如果有封面缩略图,不要直接加载原图,用 cached_network_image 配合缩略图URL,并在缓存大小上做限制:
dart复制CachedNetworkImage(
imageUrl: notice.coverUrl,
memCacheWidth: 600,
memCacheHeight: 340,
placeholder: (context, url) => Container(color: Colors.grey.shade200),
errorWidget: (context, url, error) => const Icon(Icons.image_not_supported),
)
第三,ListView.builder 的 itemExtent 如果能确定的话尽量设置,它可以让滚动引擎跳过布局计算直接预估高度。不过当列表项高度不一致时不能硬设,我这里的列表项高度比较统一,设置后滚动提升明显。
6.3 长文本与缓存策略
通知详情页的富文本渲染,如果内容特别长(比如物业发了一份几十页的停车管理办法),flutter_html 的解析性能会有明显下降。我在使用前预处理:去掉多余的空行、压缩图片尺寸标签、对超长文本分段渲染。
缓存这块主要做两个层面:
- 通知列表的缓存用Hive,拉取成功后先存本地再展示,"下拉刷新"时先读取本地缓存立即展示,再请求网络更新后对比差异,有变化就用新数据覆盖。
- 图片缓存用
cached_network_image的默认磁盘缓存,同时设置了最大缓存条数。因为小区通知里的图片会定期过期(比如停水通知的示意图),缓存长期不清理会堆积成百上千张废图。这个可以在设置页加一个"清除缓存"入口,方便用户手动清理。
6.4 底部弹窗中的TextField输入适配
最后说一个鸿蒙端比较典型的适配问题:在底部弹窗(showModalBottomSheet)里放TextField时,弹出键盘后输入框会被遮挡。这个问题在安卓和iOS上通常会自动避让,但在鸿蒙的某些系统版本上,键盘避让逻辑和Flutter的SafeArea配合得不好。
解决办法是在弹窗打开前强制设置 viewInsets 的底部padding:
dart复制showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: const ReportDialog(),
),
);
核心就是 isScrollControlled: true 加上 viewInsets.bottom 的padding。这个组合拳在鸿蒙端实测有效。如果还不行,检查你用的Flutter适配分支是否修复了相关键盘事件处理,旧版本分支确实存在键盘高度获取异常的问题。
另外,在鸿蒙设备上做调试时,我发现 debugShowCheckedModeBanner 的调试横幅在某些版本上会比其他平台更大,挡住顶部内容。发布版无影响,但调试时看着别扭,直接关掉就行。
写在后面的几点体会
这个项目做完之后最大的感受是:Flutter做跨平台开发,真正的成本不在Dart代码本身,而在"平台适配"这一层。鸿蒙适配分支的出现让Flutter开发者多了一个选择,但生态的成熟度仍需时间积累——你会遇到很多"明明在安卓上好好的,到了鸿蒙就不对"的意外情况,比如插件不支持、路径格式不同、权限声明方式不一样。
对准备做Flutter + 鸿蒙项目的团队,我的建议是:评估项目时留出20%-30%的适配工作量,不要按官方文档的理想状态来排期。团队里至少要有一个人能看懂 ohos 目录下的原生代码,因为最终很多问题都要回到原生层去排查,光会写Dart是搞不定的。
如果你正在做同类项目,可以按这个顺序推进:先把核心列表流跑通,再做详情富文本,然后接推送,最后处理打包签名。任何一个平台上的表现都以真机验证为准,模拟器不能作为最终判断依据。祝顺利。
