最近我接到一个不太一样的任务:把公司内部一直在用的商用项目看板应用,从既有 Flutter 代码库延伸到鸿蒙端。换两年前,听到“鸿蒙开发”四个字,我第一反应大概率是“又要重写一套原生界面”,但现在情况变了——Flutter 框架对鸿蒙的适配已经能支撑实际业务,跨平台方案里又多了一个值得认真评估的落地点。
这篇文章不聊空洞概念,就围绕标题里的几个关键词展开:Flutter 框架、跨平台、鸿蒙、项目看板。我会完整复盘一遍从环境准备、工程初始化、看板核心功能实现,到鸿蒙原生能力接入、调试上架遇到的问题清单。比较适合正在评估“要不要用 Flutter 做鸿蒙端”的团队,也适合已经决定上手、但还没把坑踩完的客户端开发同学。
1. 先确认业务边界和技术选型,比急着写代码重要得多
1.1 这个项目要交付的到底是什么
项目看板不是个人待办工具,它天生是多角色协作场景。老板要看整体进度,产品要拖卡片排迭代,开发要更新自己的任务状态,测试要在卡片下面挂缺陷记录。这些角色分布在手机、平板、PC 不同设备上,而且要求界面观感基本一致,不能今天 Android 上看起来是个样,明天鸿蒙上又变了另一个样。
商用两个字意味着很多“隐形需求”不是 demo 能覆盖的。除了核心的看板列、卡片、拖拽流转,还要有登录态、权限控制、图片附件上传、后端接口同步、离线缓存、操作日志、异常上报。再加上客户使用的设备五花八门,有的还在用低端机,有的已经换到鸿蒙平板,很多边界情况只能在真机上验证。
我们当时的处境是:Flutter 代码库已经维护了两三年,iOS 和 Android 两端跑得很稳,Web 端也能兼顾后台预览。现在客户明确提出需要鸿蒙端,最理想的方式当然是复用同一套业务代码,而不是让客户端团队再养一条 ArkTS 原生线。于是“Flutter 跨平台鸿蒙开发”这个命题就摆到了桌面上。
1.2 Flutter 和其他跨端方案对比,各自的账要算清楚
在确定技术路线的时候,我认真比较过几条路。第一是直接用 ArkTS 写鸿蒙原生应用,系统能力调用最顺手,HarmonyOS 的意图框架、服务卡片这些独占特性也能第一时间支持,但团队要新增一条完整的技术栈。对一个已经有 Flutter 业务代码的团队来说,等于以后每次改需求都要同步两套客户端,成本是成倍上涨的。
第二是考虑 React Native 或其它跨端框架,跨端社区确实都有人在适配鸿蒙,但很多第三方原生组件、推送 SDK、地图 SDK 并没有完整打通,商用项目一旦碰到某个关键依赖没有鸿蒙实现,就会卡在中间层进退两难。
第三就是 Flutter。Flutter 自带渲染引擎,UI 不依赖系统原生控件,这意味着在 Android 上画出来的卡片样式,在鸿蒙上大概率也能保持同样效果;而且项目看板这类应用恰好是大量自定义卡片、拖拽反馈、动画细节密集的场景,Flutter 的自绘模式天然占优。不过它的短板也很明确:鸿蒙适配仍在快速演进,大量 pub 生态里的三方插件没有鸿蒙原生实现,遇到这种情况需要自己在 MethodChannel 上补一层桥接。
三种路线放在一起,用表格会更直观:
| 对比维度 | ArkTS 原生 | React Native 跨端 | Flutter 跨端 |
|---|---|---|---|
| 系统能力接入 | 最直接 | 依赖第三方桥接 | 依赖 MethodChannel / 三方插件 |
| 多端 UI 一致性 | 各端各自维护 | 较好但组件渲染不一致 | 自绘渲染,最容易统一 |
| 现有 Flutter 代码复用 | 无法复用 | 只能复用 JS 逻辑 | 可复用绝大部分 UI 和业务 |
| 团队学习成本 | 需要新增技术栈 | 中 | 中低 |
| 拖拽/复杂列表场景 | 需要大量原生代码 | 一般 | 强项 |
| 适配鸿蒙最新能力 | 最快 | 较慢 | 中等,视社区进度 |
做完对比,结论很清楚:如果今天从零开始只做鸿蒙一个平台,ArkTS 原生不是不能选,但我们是给成熟产品增加客户端形态,而不是重新发明轮子。Flutter 在跨平台和 UI 一致性上的收益,远远大于那部分需要自己补的原生桥接成本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 鸿蒙下搭建 Flutter 开发环境,重点在版本匹配和依赖管理
2.1 先检查 Flutter SDK 和 DevEco Studio 是否匹配
鸿蒙端的 Flutter 开发环境和传统 Android 开发有一个显著差异:不能随便从官网拿一个最新版 Flutter 就当鸿蒙用,必须使用社区维护或官方发布的鸿蒙适配分支,版本和 DevEco Studio、HarmonyOS SDK 之间是绑定的。这里没有什么捷径,严格按照你安装的适配版本文档来。
我当时整理的依赖清单大概是这样的:
- DevEco Studio 5.x,包含 HarmonyOS SDK API 12 及以上;
- Flutter SDK 用适配鸿蒙的分支,而不是默认 stable 分支;
- JDK 版本需满足 DevEco 和 Gradle 的双重要求;
- 鸿蒙真机或官方模拟器,用于运行和调试。
环境变量也需要单独配置。比如把鸿蒙 SDK 的路径指给 Flutter,常见操作是执行 flutter config --ohos-sdk,指向 DevEco 自带的 SDK 目录;有些版本还需要设置 OHOS_SDK_HOME。 配置完以后,执行 flutter doctor -v,看输出里有没有 ohos toolchain 相关的可用提示。
注意:如果你刚安装完 Flutter,命令行提示找不到
flutter命令,别急着怀疑安装包坏了,大概率是 PATH 环境变量没有在已经打开的终端里刷新。新开一个终端窗口再执行一次,通常就正常了。这个细节很小,但第一次接触 Flutter 的人几乎都会遇到。
2.2 初始化同时包含 Android、iOS、鸿蒙的工程
当 Flutter 鸿蒙分支准备好之后,初始化工程和传统 Flutter 项目很相似,只是多了一个鸿蒙平台目录。通常可以用下面的命令创建:
bash复制flutter create --platforms=android,ios,ohos,web board_app
如果你用的是适配版本的 Flutter,ohos 会作为一个可识别平台出现在参数里。生成后的工程会多出一个 ohos 目录,这个目录就是鸿蒙原生侧,需要导入 DevEco Studio 进行编译签名和运行。
第一次跑通流程时,千万不要一上来就堆业务代码。先创建一个最小工程,在鸿蒙模拟器或者真机上把默认的计数器页面跑起来,确认 Flutter engine 能在鸿蒙上正常启动,hot reload 能连上,再开始填业务逻辑。这一步看着简单,但它能提前暴露大量环境层面的问题,比如 SDK 路径不对、签名证书没配置、模拟器连接不上等等。
我印象最深的一版流程是先在终端里用 flutter run -d <device> 启动,然后切到 DevEco Studio 里看鸿蒙侧的运行日志。两边的输出都要盯住,因为 Flutter 引擎跑起来后应用的“门面”是 Dart 侧的,但 shell 进程仍然在鸿蒙原生的 Ability 里。
2.3 依赖管理和版本锁定,这步几乎决定了后面顺不顺
鸿蒙适配版 Flutter 对 pub 生态的兼容性整体不错,但有三方原生依赖时仍需格外小心。普通 Dart 包一般直接能用,比如状态管理、日期格式化、网络库,这些都没问题;一旦引入带 Android/iOS 原生代码的插件,就要谨慎检查它的鸿蒙适配情况,否则编译阶段会报各种平台文件缺失。
实际操作中,我建议把 pub 镜像配置成国内可达的镜像源,避免依赖下载超时:
bash复制export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
镜像这个问题要注意:只是加速下载,不改变包的行为。如果某个插件版本在鸿蒙编译时报错,先不要怀疑镜像,而是优先看插件里是否包含 ohos 目录。没有鸿蒙原生实现的插件,镜像地址换成什么都没用。
依赖版本锁定是另一个容易踩坑的地方。同一个业务功能,在 Android 端用某个插件的 1.2.0,在鸿蒙端升级到 1.3.0 之后反而无法编译,这种事我遇到过不止一次。后来我要求项目里所有带原生代码的依赖,都精确锁定版本号,不放 ^ 符号,升级必须单独走一次回归测试。别小看这个习惯,商用项目最怕“一切正常,直到发布前突然冒出一个编译错误”。
3. 看板核心功能实现:数据模型、多列 UI、拖拽和状态
3.1 看板数据模型怎么设计才够商用
看板应用的核心其实不是 UI,而是数据模型。一个看板通常由多个列表列组成,每一列下有若干卡片,卡片之间有顺序,卡片又关联负责人、标签、截止日期、评论数等多个信息。在商用场景中,还要考虑离线操作和服务端同步,所以数据模型从一开始就不能写得太死。
我一般把卡片定义成这样的结构:
dart复制class KanbanCard {
final String id;
final String columnId;
String title;
String description;
List<String> tagIds;
String? ownerId;
DateTime? dueDate;
int commentCount;
int orderIndex;
bool isLocalModified;
}
columnId 用来记录卡片在哪一列,orderIndex 用来记录卡片在同一列里的排序位置。很多新手会把卡片顺序直接存在数组下标里,这个设计在拖拽跨列之后非常痛苦,因为每次移动都要重新排列所有卡片的下标。用独立的 orderIndex 字段,移动卡片时只需要修改目标列里少数几张卡片的排序值,后端同步也只需要传变更部分。
列本身也可以定义得更健壮一些:
dart复制class KanbanColumn {
final String id;
String name;
int? wipLimit; // 进行中的卡片数量上限,很多团队有这个约束
List<KanbanCard> cards;
}
商用看板还常有一些辅助信息,比如每列的颜色区分、卡片是否被当前用户订阅、评论里有没有 @ 到当前用户。这些字段不需要一开始全部加进去,但数据模型要预留扩展空间,不建议把卡片做成纯 KV 的 Map,否则后面维护会非常痛苦。
3.2 多列看板 UI 搭建:横向滑动的列表迷宫
看板 UI 的直观感受是一行一行的列,每列可以上下滚动,底部还可以横向滑动查看更多列。这个交互在 Flutter 里实现起来不复杂,但有几个细节很影响手感。
我的结构大致是:最外层是一个横向滚动的 ListView,每个 item 是一个固定宽度的列容器,宽度在手机端取屏幕宽度的 85% 左右,在平板和桌面端可以固定到 300 到 340 像素。列容器内部放一个纵向滚动的 ListView,渲染这一列下的卡片。
列容器外层还要处理一件事:拖拽卡片跨列时,当用户把卡片拖到屏幕边缘,需要自动横向滚动列表。这一步 Flutter 默认不会帮你做,需要在拖拽过程中判断手指位置,调用横向滚动控制器进行移动。做不好就很尴尬——用户想从第一列拖到第三列,拖到屏幕边缘发现滚不动,只能放下卡片再手动滑过去。
卡片本身的视觉样式是整个看板的脸面。状态标签、负责人头像、截止日期、评论图标,这些在卡片上要有清晰的分层。我习惯把卡片根节点做成一整块可点击区域,点击之后进入详情页,长按则触发拖拽。这里需要注意一个交互冲突:卡片内部如果还有可点击的子组件,比如标签上的删除按钮,长按手势会很不稳定。我当时的处理是:标签和头像只做展示,所有操作统一放到详情页里。
3.3 跨列拖拽的实践:LongPressDraggable 和 DragTarget 的组合
拖拽是看板应用最核心的交互,没有之一。Flutter 自带的 Draggable 和 DragTarget 抽象已经足够支撑这个需求,但我们需要的是长按卡片再拖动,而不是手指一放上去就开始拖,所以正确选择是用 LongPressDraggable。
核心写法大致是这样的:
dart复制LongPressDraggable<KanbanCard>(
data: card,
feedback: Material(
elevation: 6,
borderRadius: BorderRadius.circular(12),
child: CardPreview(card: card, width: 280),
),
childWhenDragging: Opacity(opacity: 0.3, child: CardPreview(card: card)),
child: CardPreview(card: card),
);
feedback 是拖拽过程中跟随手指移动的组件,它脱离原来的布局树,所以必须用 Material 包裹,否则阴影和圆角会出现渲染问题。childWhenDragging 是原位置留给用户的“残影”,这里我经常看到一个错误做法是留空,视觉上卡片突然消失,用户会以为操作失败了。我习惯把原卡片降透明度到 0.3 左右,既有反馈又不遮挡视线。
接收拖拽的每一列是这样处理的:
dart复制DragTarget<KanbanCard>(
onWillAcceptWithDetails: (details) {
return details.data.columnId != column.id;
},
onAcceptWithDetails: (details) {
_moveCardToColumn(details.data, column.id);
},
builder: (context, candidateData, rejectedData) {
final isHighlight = candidateData.isNotEmpty;
return Container(
color: isHighlight ? Colors.blue.withOpacity(0.08) : Colors.transparent,
child: _buildColumnContent(),
);
},
);
onWillAccept 里会过滤掉“本来就在这一列”的卡片,避免无效移动。builder 中的 candidateData 用来检测当前有没有拖拽中的卡片悬浮在这列上方,有就给列背景加一个高亮色,提示用户“你可以放这里”。
这块有几个容易踩的坑:
feedback的大小不要直接用整张卡片,最好是一个固定宽度的预览卡片,太大会挡住用户看目标列位置;- 如果同时存在横向滚动和纵向滚动,长按拖拽手势容易被滚动守卫拦截,实测中需要给
LongPressDraggable配上合适的dragAnchorStrategy; - 卡片移动到目标列时,不能只把卡片塞进目标列末尾,更要根据手指落点决定插入到哪张卡片之前。这个逻辑我最初简化掉了,结果产品验收时被吐槽“拖完卡片顺序不对,还得手动再调整”。
插入位置的判定不复杂:在目标列的 DragTarget 上,用 onMove 或拖动过程中的坐标去遍历当前列所有卡片的 RenderBox 位置,找到手指所在的索引即可。前提是当前列的所有卡片都渲染在屏幕上,如果列里有几百张卡片,懒加载模式下有一部分并没有真正 build,此时就要权衡,是先滚动加载还是用一个虚拟的按 orderIndex 计算插入位置。
3.4 状态管理选型:项目从 Provider 切到 Riverpod 的心路
初期项目看板的状态量不大,我用 Provider 就够了。卡片数据、当前用户、看板配置,每个 Provider 管一块,组件里通过 context.watch 去拿。但业务越往后越复杂的时候,问题就暴露了:看板卡片移动后,紧接着要触发后端同步、更新评论未读数、刷新用户最近访问列表、推送通知,多个状态互相联动,用 Provider 写起来很快会陷入回调地狱。
后来我把状态层切到了 Riverpod,本质上换汤不换药,但它的 AsyncNotifier 和依赖注入机制明显更适合异步场景。比如加载看板数据这一件事:
dart复制class BoardController extends AsyncNotifier<BoardState> {
@override
Future<BoardState> build() async {
final repo = ref.watch(boardRepositoryProvider);
final board = await repo.fetchBoard();
return BoardState(board: board);
}
Future<void> moveCard(String cardId, String targetColumnId) async {
// 先更新本地状态,立即反馈 UI
// 再调用后端同步,失败时回滚并提示
}
}
商用应用最重要的是“用户操作不能被网络阻断”。拖拽完卡片,UI 必须马上改变,不能等后端接口返回 200 才移动位置。我采用本地先行更新、后端异步同步、失败自动回滚的策略。这里需要状态层支持乐观更新,Riverpod 的 update 方法操作起来很顺手,Provider 的 ChangeNotifier 也能做到,只不过代码会稍微啰嗦一些。
另外提一句:看板项目的状态域一定要隔离,不要把“卡片移动”和“侧边栏开关状态”混在同一个全局状态类里,否则团队多人协作时合并冲突会持续不断。
4. 鸿蒙原生能力接入和商用化必备的细节
4.1 从鸿蒙图库选择附件图片,需要自己写一层桥接
项目看板里,用户上传附件图、更换头像都是高频操作。在 Android 上可以直接用 image_picker,iOS 上也没问题,但在鸿蒙上,这类带原生实现的插件就未必好使了。我一开始图省事直接引了某个插件,编译倒是通过了,点击图库后没有任何反应,最终定位是插件没有鸿蒙端的实现,方法调用根本没有被转发到系统。
这种场景必须自己写 MethodChannel。跨平台侧的调用封装起来并不复杂:
dart复制const _channel = MethodChannel('com.example.board/gallery');
Future<String?> pickImage() async {
try {
final path = await _channel.invokeMethod<String>('pickImage');
return path;
} on PlatformException catch (e) {
debugPrint('pick image failed: ${e.message}');
return null;
}
}
难点在鸿蒙原生侧如何实现这个 channel。HarmonyOS NEXT 的应用需要使用照片选择器能力,具体实现会落在 harmonyos 原生工程里,注册 MethodCallHandler,收到 pickImage 后拉起系统的 PhotoViewPicker 或 PhotoAccessHelper。
这里我给不了你一份可以复制粘贴就完事的代码,因为不同 SDK 版本 API 差异比较大。但思路是通用的:在鸿蒙工程的模块初始化处注册与 Dart 侧同名的 MethodChannel,然后在调用系统相册的异步回调里把图片的临时访问地址回传给 Flutter。如果图片需要缓存到本地再上传,还要考虑沙箱读写权限的问题。第一次调通这个链路,我建议把 dart 侧返回的 path 先打日志,确认是不是 file:// 开头,鸿蒙返回的路径格式和 Android 不太一样,如果直接拿去给图片库加载可能会失败。
注意:新版本鸿蒙对相册权限的管控有变化,不一定需要申请
ohos.permission.READ_IMAGEVIDEO,尤其是用系统 PhotoViewPicker 的时候。我实践中更推荐用 picker 方式,用户主动选择图片,权限合规上也省去很多麻烦。
4.2 微信登录在鸿蒙端的适配思路
商用应用基本绕不开登录。如果你们的 App 目前用的是 Flutter 微信登录,在 Android 和 iOS 上跑得好好的,千万不要默认它在鸿蒙上也能一键搞定。微信开放平台对鸿蒙生态的 SDK 支持情况需要单独查,而且鸿蒙应用包名、签名、回调 scheme 和 Android 是完全隔离的。
我当时做了一个折中方案:登录相关的共用逻辑放 Dart 层,调用一个统一的抽象接口,由各端实现真正的原生登录调用。Android/iOS 端继续走原来的微信 SDK 插件,鸿蒙端则根据当前项目使用的账号体系决定接什么。
如果你的应用接的是鸿蒙账号服务,而且后端只认自己服务器的 token,这里的流程比想象中绕:鸿蒙 SDK 登录成功返回授权码或用户信息后,还需要在后端把鸿蒙的凭据交换成业务 token。记住一点:不能把鸿蒙端登录直接当成 Android 端微信登录处理,客户端拿到的 openId/unionId 体系可能完全不同,这个字段映射关系要和后端对齐,否则同一个用户在两台设备上会变成两个账号。
如果项目还没真正接第三方登录,我建议从需求层面先跟产品确认:企业内部的商用看板是否真的非用微信登录不可?很多内部工具最后都改用手机号验证码或企业 SSO 登录,反而省掉一整条鸿蒙适配链路。
4.3 设置页和许可页的几个 UI 细节,别拖到上架前再改
商用 App 几乎都会带一个“关于”页面,里面要放开源许可列表。Flutter 提供 showLicensePage,但这个页面默认的观感可能和你的 App 主题不太协调,尤其是标题栏颜色、返回按钮样式。它在内部使用的仍然是上层 Theme,所以一种比较直接的方案是调用前包一层自定义 Theme:
dart复制showLicensePage(
context: context,
applicationName: 'Board App',
applicationVersion: '2.1.0',
applicationLegalese: 'Copyright ...',
);
如果默认页面里文字样式和背景色不合预期,可以通过 Theme(data: ThemeData(...), child: Builder(...)) 包裹后再调用,页面的 AppBar 和正文会自动继承。
还有一个看似不相关但真实发生过的 UI 问题:如果设置页某个分组用了 CheckboxListTile,你会发现复选框和文字之间的距离有时候会让强迫症很难受。这个距离主要受 dense、contentPadding 和 controlAffinity 影响。想要更紧凑的排版,可以这样调整:
dart复制CheckboxListTile(
dense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 12),
controlAffinity: ListTileControlAffinity.leading,
title: Text('接收邮件通知'),
value: _notifyByEmail,
onChanged: (v) => setState(() => _notifyByEmail = v ?? false),
);
controlAffinity 控制复选框在左侧还是右侧,contentPadding 控制整体左右间距。这些不是大问题,但如果你设置页条目很多,每个条目的 checkbox 和文字之间距离忽大忽小,整体质感会很减分,也是走查阶段最容易被吐槽的点。
4.4 商用要求不能省:崩溃监控、埋点与多语言
商用应用最怕的不是功能少,而是出问题以后没人知道。真机用户环境千差万别,有些卡死只在特定机型上出现,开发者永远复现不了。Flutter 侧的全局异常一定要兜住,至少做到不闪退并留下日志。
我在项目里做了两层兜底。第一层是 Dart 侧抓未捕获异常:
dart复制void main() {
runZonedGuarded(() async {
FlutterError.onError = (details) {
// 上报到自己的日志服务
CrashReporter.instance.report(details.exception, details.stack);
};
runApp(const BoardApp());
}, (error, stack) {
CrashReporter.instance.report(error, stack);
});
}
第二层是原生侧的崩溃捕获。鸿蒙上如果要快速接入,可以优先看 Sentry 等工具是否有鸿蒙 SDK;如果暂时没有,也至少要确保原生崩溃日志能写入本地文件,下次 App 启动时上传。商用看板里最关键的埋点事件是这些:登录成功/失败、看板加载耗时、卡片拖拽移动成功/失败、附件上传失败。埋点层的封装要考虑跨端复用,不能只在 Dart 层散落打印日志,否则后续看数据分析时什么维度都拿不出来。
多语言方面,鸿蒙系统返回的语言标签可能和 Android 返回的不完全一致。建议统一在 Flutter 侧用 Window 的可访问性或 intl 包处理,不要让业务代码直接依赖系统 locale 的格式。我遇到过测试同事用繁体环境打开应用,部分系统弹窗是简体、部分是英文的问题,排查下来就是某些插件直接从原生侧拿了系统 locale,没有走应用自己的语言资源。这个在商用上架前必须过一遍多语言测试用例。
5. 构建、调试与真机问题,把常见坑提前列成清单
5.1 鸿蒙模拟器和真机的调试方式,和传统 Android 不太一样
鸿蒙开发环境搭好后,你在 Flutter 侧执行 flutter devices,如果能识别到鸿蒙模拟器或真机,就可以直接通过 flutter run 启动。但有一点和 Android 调试有差异:原生侧和 Dart 侧是两个调试器。
在 Flutter 代码里打断点,用 VS Code 或者 Android Studio 的 Dart 插件都可以,但如果你想调试鸿蒙原生侧,比如看 MethodChannel 收到消息后是否拿到了正确的图片路径,那就必须切到 DevEco Studio 里去打开 ohos 工程,在 .ets 文件里打断点。两套引擎之间,日志也是分开的。刚开始不习惯,经常出现 Dart 侧打了 log 没输出,以为程序挂了,其实只是看错了日志窗口。
鸿蒙真机调试还需要注意签名配置。DevEco 默认的自动签名可以让你快速跑起来,但到了发布阶段必须换成正式的发布证书。如果你有两台鸿蒙设备,务必先写完自动化或手动回归流程再分发测试包,签名不一致的设备装不上同一个 hap,测试同学很容易误判成“包坏了”。
5.2 编译报错和依赖下载失败,先看完这张排查清单
这段时间踩过的问题五花八门,我整理了一个高频问题表,遇到类似报错可以先对着查:
| 现象 | 常见原因 | 解决思路 |
|---|---|---|
flutter 命令找不到 |
安装后未刷新终端 PATH | 新开终端,或重启编辑器 |
| 依赖包下载卡住或失败 | 网络环境访问 pub 不稳定 | 配置国内镜像源,清理 pub 缓存后重试 |
| 依赖版本冲突导致编译失败 | 各依赖版本不兼容 | 锁定 pubspec 中版本号,不用 ^ 浮动 |
| Gradle 报 “apply script” 相关错误 | Flutter Gradle 插件接入方式过旧 | 按新版插件配置方式迁移到 plugins block |
| CMake 报找不到 Visual Studio generator | Windows 上某些插件需要 C++ 工具链 | 安装对应 VS 版本和 C++ 桌面开发组件 |
| 鸿蒙运行后页面白屏 | Flutter engine 或 ohos 目录注册问题 | 先在空工程跑 hello world,再排除业务代码 |
| 图库或相册调用无反应 | 插件没有鸿蒙原生实现 | 自行编写 MethodChannel 桥接 |
flutter clean 和 flutter pub cache clean 是排查依赖问题的两板斧,但不要一上来就用。先试试删除 pubspec.lock 重新生成,能解决大部分版本漂移问题。如果某个包是特定 commit 才支持鸿蒙,直接在 pubspec 里引用 git 地址也是可以的,但要保留锁文件,防止别人拉代码后自动切到错误版本。
编译期间的告警不要一律无视,尤其当你看到某个日志里提到“Falling back to ...”、“not found in ohos”时,多半意味着某个原生能力没有真正接到鸿蒙平台上,这次能编译成功是靠 fallback 方案撑过去的,后续运行时大概率会出问题。
5.3 卡片数量多了以后,看板滑动卡顿怎么办
项目看板在真实使用中,一个迭代列可能挂上百张卡片。如果直接往 ListView children 里塞大量卡片,内存和帧率都会出问题。Flutter 的懒加载机制要求我们始终使用 ListView.builder,让它按需构建卡片。此外还有几个优化技巧。
卡片组件要做成 const 构造函数,不变的部分就别重复 rebuild。我当时给卡片组件加了状态缓存,只有拖拽、更新字段才触发生成新的卡片实例,普通滚动时复用已有组件,滑动明显更跟手。
图片附件是一个容易被忽略的性能瓶颈。卡片上的缩略图不能直接加载原图,服务端如果有图片处理能力,拼接固定宽高的缩略图参数;没有的话,客户端也要自己压缩并缓存到本地。否则用户翻到一张 2MB 的原图卡片,列表就掉帧,体验极差。
拖动预览的 feedback 组件同样要控制大小。它是在 overlay 上绘制的,本身
