近半年我一直在折腾一个偏冷门的组合:用 Flutter 开发 OpenHarmony 应用。冷门到什么程度?网上搜“Flutter for OpenHarmony”能搜到的实战文章屈指可数,大部分是环境搭建教程,一进入业务逻辑就断片了。我手里这个看书管理记录 App 已经做到了书籍列表这一层,过程中踩了不少坑,也积累了一些可复用的经验。这篇文章就围绕“书籍列表”这个看似简单、实则五脏俱全的功能,完整走一遍实现链路——从环境准备、数据模型设计、列表 UI 构建,到组件通信、下拉刷新、联合搜索排序,再到 OpenHarmony 上架前必须面对的 XTS 认证适配。适合两类人看:一是想在 OpenHarmony 上跑 Flutter 的开发者,二是在传统 Flutter 项目里想做列表页但总感觉差点章法的朋友。我会把关键代码、踩坑记录、选型理由都摊开讲,尽量让你照着就能推。
1. 为什么偏偏是 Flutter for OpenHarmony:选型背后的现实考量
这个标题摆出来,估计很多人第一反应是“为什么不直接用 ArkUI 写?”或者“OpenHarmony 不是有自己的原生开发栈吗?”。我先把这个最核心的选型问题讲透,因为后面所有技术决策都建立在这个基础上。
1.1 一套代码多端复用的真实诱惑
我这套看书管理记录 App 不是只跑 OpenHarmony 一个平台。书架数据、阅读进度、笔记标注这些核心逻辑,我在安卓和 iOS 上都有需求,后期还可能要覆盖 Windows 桌面端。如果每个平台都维护一套原生代码,光是书籍列表这种页面就要写三遍:安卓的 RecyclerView、iOS 的 UITableView、OpenHarmony 的 List 组件,每一遍还都要处理下拉刷新、空态、加载态这些交互细节。养过原生多端项目的人都懂,这不仅是工作量翻倍,而是后续每一次需求变更都要同时改三处,漏改一处线上就出问题。
Flutter 的价值恰恰在于渲染层和应用层逻辑的跨端复用。OpenHarmony 的 Flutter 适配方案(社区项目 flutter_flutter 和 OpenHarmony 的 flutter 适配层)在引擎层面做了移植,Dart 层 API 基本保持一致。这意味着我的 Model 层、Repository 层、状态管理逻辑,以及绝大部分 Widget 代码,可以原封不动地跨端复用。实际跑下来,我的书籍列表页在 OpenHarmony 和安卓上的共享代码超过 90%,这个数字非常可观。
1.2 OpenHarmony 生态的现状:原生开发栈不够成熟
说实话,OpenHarmony 的 ArkUI 声明式开发已经做得不错了,但横向对比 Flutter 的生态积累,差距还是明显的。我举几个实际遭遇:
- 三方组件库稀缺。我需要一个评分星星组件、一个书籍封面的圆角裁剪、一个书架网格的拖拽排序,在 Flutter 的 pub.dev 上都是现成的,而在 ArkUI 生态里要么自己造轮子,要么从零手写。
- 调试工具链。Flutter 的 DevTools 在内存分析、Widget 树检查、性能时间线上非常成熟,而 OpenHarmony 原生侧的调试工具还在快速迭代。
- 团队技术栈复用。我团队里几个人都是 Flutter 出身,让他们转 ArkUI 不现实,用 Flutter 做 OpenHarmony 是平滑过渡。
1.3 Flutter 适配 OpenHarmony 的实际成熟度:能跑但要用巧劲
适配层目前的状况是“能跑、可上架、但需要知道边界”。基础渲染、事件派发、Platform Channel 这些核心链路是通的,但有几个明显要注意的地方:
先看渲染引擎。OpenHarmony 适配的 Flutter 引擎集成了自研的图形渲染能力,对标的是 Flutter 标准的 Skia 渲染。在文本排版、遮罩、边框这些常见场景下看不出区别,但如果你用到比较新的 Impeller 渲染后端的某些特性,就要先验证兼容性。我建议锁定一个稳定的 Flutter 版本,不要追新。我目前用的是 3.7.12 对应的 OpenHarmony 适配分支,稳定性和性能都验证过了。
再看原生插件。OpenHarmony 的 Flutter 插件生态还在爬坡期,很多 Flutter 插件因为依赖了安卓的特定 API 或 iOS 的特定 Framework,无法直接跑通。我的策略是:凡是涉及文件读写、数据库、网络请求这类基础能力的,优先确认是否有 OpenHarmony 适配版;实在没有的,用 Platform Channel 自己封装一层薄壳。后面讲书籍列表实现时,你会看到我是怎么在纯 Flutter 能力范围内把功能做完整的。
这套选型不是“哪个好”的单纯比较,而是“哪个适合我现在的约束条件”的务实取舍。如果你也是多端需求 + 团队 Flutter 背景 + 对 OpenHarmony 原生栈不熟,这个组合很值得一试。
2. 环境与工程搭建:最容易卡住新手的几个暗坑
标题里包含“实战”,就不能跳过环境搭建,但我也不会像官方文档那样面面俱到,只讲我实际踩过、花费超过半天时间排掉的几个坑。这几个坑偏门但致命,不解决连 hello world 都跑不起来。
2.1 DevEco Studio 与 Flutter 插件的版本匹配
OpenHarmony 应用开发默认用 DevEco Studio,而 Flutter 侧需要安装 OpenHarmony 的 Flutter SDK 和对应的 IDE 插件。版本匹配是个重灾区。我一开始装的是 DevEco Studio 4.0 配 Flutter 3.7.12 的 OpenHarmony 分支,结果插件加载不了,提示 API 版本不兼容。
提示:DevEco Studio 和 Flutter 插件的版本匹配关系不是线性对应的。建议先查一下你目标 Flutter 版本的官方适配说明,确定对应的 DevEco Studio 版本,再动手安装。装错了最典型的症状是 Flutter 项目无法识别 OpenHarmony 设备,IDE 里没有 harmonyos 运行目标。
我的最终组合是:DevEco Studio 4.0 Release + Flutter 3.7.12 OH 分支 + OpenHarmony SDK API 9。这个组合我用了两个多月,期间没有出过环境层面的幺蛾子。
2.2 用命令行创建工程而非 IDE 向导
很多教程教你打开 DevEco Studio 的向导去建 Flutter 工程,但我强烈建议用命令行:
flutter create --platforms ohos books_app为什么?IDE 向导创建出来的工程默认是标准安卓/iOS 工程加一个 handover 目录,有时候会自动配置一些 IDE 相关的启动参数,反而掩盖了底层的编译流程。命令行创建更干净,生成的结构我能完全掌控。生成之后,用 DevEco Studio 打开工程根目录,让它自动识别 ohos 模块。
这里有一个关键细节:命令里的--platforms ohos需要你的 Flutter 环境里已经装好 OpenHarmony 适配版的 Flutter SDK。你可以通过flutter doctor确认OpenHarmony这一项是不是绿勾。
2.3 设备连接与调试运行的完整链路
真机调试是另一个劝退点。OpenHarmony 设备(我用的是润和 DAYU200 开发板)连接电脑后,首先要确认hdc命令可用:
hdc list targets如果看不到设备,大概率是 hdc 版本和设备的 SDK 版本不匹配。我的经验是直接用 DevEco Studio 自带的 hdc 工具,把它的路径加到环境变量里,避免用错版本。
接下来在 Flutter 侧运行:
flutter run -d <device-id>这一步如果报Unable to connect to OpenHarmony device,可以先在 DevEco Studio 里手动跑一次原生工程,确认设备连接和签名配置正常,再回到 Flutter 侧。为什么?原生工程跑通说明设备链路没问题,问题就缩小到了 Flutter 适配层。
2.4config.json模块配置:模块名和包名的坑
OpenHarmony 工程里有config.json,类似安卓的AndroidManifest.xml。Flutter 自动生成的配置大体可用,但有几个字段需要你手动修正:
module.mainElement指向的入口页面是否要改成你自己的首页;deviceType是否包含你目标设备类型(默认有 phone);- 请求的权限是否最小化。
我最开始没动config.json,直接跑原生空工程没问题,但 Flutter 侧跑起来之后页面空白。排查了半天发现是abilities里配置的orientation锁死成了横屏,而我 Flutter 代码里设的是竖屏,渲染出来就是一片空白。这种问题的排查思路:先看 DevEco Studio 的日志,再逐行看 Ability 配置,而不是在 Flutter 代码里找原因。
3. 书籍列表的数据层设计:Model、Repository 与内存缓存
列表页看起来是“把数据铺在屏幕上”,但铺之前有两件决定后续开发效率的事必须先做好:数据模型怎么定义,数据从哪来、怎么管理。我的做法是标准的 MVVM + Repository 模式,在 Flutter 里对应 Model + Provider + Repository。
3.1 BookModel 字段设计:不用 JSON Serializable 也能优雅处理
看书管理记录 App 的书籍对象,我最终定的核心字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 全局唯一 ID,由服务端或本地生成 |
| title | String | 书名,列表主展示字段 |
| author | String | 作者,列表副展示字段 |
| coverUrl | String | 封面图 URL,兜底用本地占位图 |
| rating | double | 评分,0-5 分,用于排序和展示 |
| category | String | 分类标签,筛选和分组用 |
| readingProgress | int | 已读页码,0 表示未开始 |
| totalPages | int | 总页数,算阅读百分比 |
| updatedAt | DateTime | 最后阅读时间,排序用 |
| isFavorite | bool | 是否加入收藏,书架分组用 |
字段设计上,有人喜欢用json_serializable自动生成解析代码,我这次没走这条路。原因是项目不大,手写fromJson和toJson的代码量完全可以接受,而且避开了 build_runner 在 OpenHarmony 适配环境下的潜在兼容性问题。等你哪天把上游依赖升级了,build_runner 报错烦死你,不如直接写死。
3.2 Repository 的接口设计:为将来换数据源留好余地
Repository 这一层我定义得很薄,但接口必须稳定。它的核心职责是“屏蔽数据来源”,让上层 UI 不关心数据是来自网络、数据库还是本地文件。
abstract class BookRepository { Future<List<BookModel>> fetchBooks({String? keyword, BookCategory? category, BookSortOption sort}); Future<BookModel> addBook(BookModel book); Future<BookModel> updateBook(BookModel book); Future<void> deleteBook(String id); }当前的实现是一个LocalBookRepository,用 sqflite 存 SQLite 数据库。为什么用 sqflite?因为它在 OpenHarmony 上通过sqflite_ohos这个适配包可以跑通,而且在纯 Dart 层的调用方式和安卓完全一致。后面如果你想接后端,只需要再写一个RemoteBookRepository,UI 层完全不用动。
3.3 内存缓存与加载状态的演进:从“一次性加载”到“增量同步”
最开始我图省事,进页面就一把梭把所有书查出来放内存里。书量少的时候没问题,但总量一多,SQLite 查询加 JSON 解析会让首帧时间明显变长。后来我做了两层优化:
第一层是纯内存缓存。App 启动时加载一次全量数据存入内存 Map,之后列表页的每一次进入、搜索、排序都直接查内存,不再碰数据库。体验提升非常明显,几乎无感知加载。
第二层是懒加载分页。Long 列表用ListView.builder配合ScrollController,在滚动到距离底部还有 200 像素时触发加载下一页。LocalBookRepository的fetchBooks支持limit、offset参数,数据库层用LIMIT/OFFSET做分页查询。
这两层叠加后的效果是:首帧只需要等到内存缓存就绪,列表滚动过程平滑不卡顿,交互响应速度完全可以接受。
4. 列表 UI 的完整实现:从简单 ListTile 到网格卡片
书籍列表的 UI,最朴素的方案是ListTile一行一个书名,但真做一个面向阅读管理的 App,这样太简陋了。我最终实现的是经典“网格卡片”布局,包含封面、书名、作者、评分、阅读进度条。这个 UI 说难不难,但不注意细节很容易做出一种“半成品”的感觉。
4.1 布局选型:GridView 和 ListView 的取舍决策
先定框架:书架场景最适合的是网格布局(GridView),一眼能看到多本书的封面,有翻书架的感觉。我选了GridView.builder加SliverGridDelegateWithFixedCrossAxisCount,跨平台都一致。
GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 12, crossAxisSpacing: 12, childAspectRatio: 0.62, ), itemBuilder: (context, index) => BookCard(book: books[index]), )这里关键参数是childAspectRatio。封面图标准比例一般是 3:4,但卡片里还有书名、评分、进度条,得把竖向空间留够。我调了半天,最终定在 0.62(宽高比),卡片整体不拥挤也不空旷。
4.2 BookCard 组件的三个坑:封面缓存、文字截断、点击反馈
封面图的加载我用的是cached_network_image的 OpenHarmony 适配版cached_network_image_ohos。第一次从网络拉,之后走本地缓存,滚动起来不会白屏闪烁。封面加载失败时给一个灰色占位块,避免图片区空着难看。
书名和作者显示用Text组件,必须加上maxLines和overflow。书名最多两行,作者一行,超出部分省略号。不加这两个属性,超长书名会把卡片撑变形。
点击反馈用InkWell。很多人觉得 InkWell 就是包一层的事,但如果你在卡片里用了圆角容器,要记得在Material组件的borderRadius上同步处理,不然点击时水波纹会盖出直角边框,非常丑。这个细节没注意的话,你会在各种帖子底下看到类似“InkWell 的水波纹怎么有方角”的求助。
4.3 空态、加载态和错误态:列表页的三位一体交互
书籍列表不可能永远有数据。用户刚安装完、一本都没录入的时候,页面就是空荡荡一片。我的空态设计是一段引导文字加一个“添加第一本书”按钮,把新用户直接导流到录入流程。
加载态我做了个LoadingState枚举,配合AnimatedSwitcher做平滑过渡:
enum LoadingState { loading, success, empty, error }这个枚举贯穿整个列表页状态管理,好处是搜索、刷新、首次加载都能复用同一套 UI 逻辑。错误态给个“重试”按钮,点击后重新触发数据加载。
5. 列表页的核心业务逻辑:搜索、排序与分类联动
列表页不是静态展示,真正的核心是“怎么让用户快速找到他想看的那本书”。我把搜索、排序、分类筛选做成了三套独立逻辑,通过一个统一的 Controller 联动。这一节的思路可以完全复用到你自己的列表页。
5.1 搜索方案:防抖和“先内存后数据库”的两段式搜索
搜索框我用TextField监听输入,每次输入都触发搜索会导致两个问题:一是 key 输入过程中的中间态都会去查数据,浪费算力;二是数据库查询结果回传顺序可能错乱——前一次查询比后一次慢,后输入的结果反而先显示,UI 闪跳。
我的方案是防抖 + 内存优先。防抖用Timer实现,停 350ms 才触发搜索。内存优先的意思是:先在内存缓存里用where过滤标题、作者、分类字段,如果用户敲的字很长或过滤结果为空,再走数据库的LIKE查询。这个设计保证大多数字输入在 10ms 内出结果,数据库兜底也不会出乱子。
5.2 排序选项与 SortOrder 枚举
排序我支持三种:按最近阅读时间、按评分、按书名。对应到 Dart:
enum BookSortOption { recent, rating, title }排序发生内存中,因为数据量不足以让排序成为瓶颈。List.sort是稳定的,先用updatedAt排,再叠加rating做次排序,保证体验一致。
这里有一个用户体感细节:按评分排序时,相同评分的书之间按什么排?我选择了“最近阅读优先”,这样每次进列表看到的顺序是稳定的,而不是随机跳动。
5.3 分类筛选和搜索的组合状态
书籍分类用的是ChoiceChip一行,点选一个分类就过滤一类书。这个组件本身简单,但和搜索组合时状态管理要小心——搜索条件和分类条件是“且”关系,需要放在同一个 State 对象里。我用 Provider 的ChangeNotifier把这三个状态统一管起来:
class BookListController extends ChangeNotifier { String keyword = ''; BookCategory? selectedCategory; BookSortOption sortOption = BookSortOption.recent; List<BookModel> books = []; bool get isEmpty => books.isEmpty; }任何条件变化,notifyListeners,列表重新渲染。整个逻辑简单、直观、不绕。
6. 组件通信与状态管理:Provider 模式下父子组件的协作姿势
热搜词里有“flutter组件通信”,这个话题在列表页实现里几乎必须面对:搜索框、分类筛选条、网格列表、底部排序菜单,它们彼此没有直接嵌套关系,但状态是共享的。我用了 Provider 管理全局状态,组件通信严格控制在两层:跨组件通过共享 Controller,父子组件通过构造参数传递回调。
6.1 为什么没选 Bloc / GetX / Riverpod 这套组合拳
这几个方案我都用过,简单说下选型逻辑:
- Bloc 适合大项目,事件驱动清晰,但样板代码太重。一个列表页就要写
BookEvent、BookState、BookBloc三件套,还要做Equatable,对于这种体量的页面没必要。 - GetX 上手极快但隐式依赖太多,路由、状态、依赖注入全挂在一个全局实例上,团队协作时容易写出“一人写代码、十人看不懂”的灾难现场。
- Riverpod 是 Provider 的升级版,编译期安全好,但我用的时候还在 2.x 版本演进期,API 变了好几次,文档跟不上。
最终选了 Provider,理由很朴素:API 稳定、社区认知度高、学习曲线平缓,而且ChangeNotifier的响应式模型足够覆盖列表页需求。
6.2 跨组件状态同步:共享 Controller 而非层层回调
搜索框在列表页最顶部,分类筛选条在搜索框下面,Grid 列表占中间大片区域。如果每一层都手动传递回调,代码会变成回调地狱。我的做法是把BookListController用ChangeNotifierProvider挂在页面顶层,所有子组件通过context.watch或context.read访问同一个实例:
ChangeNotifierProvider( create: (_) => BookListController(repository), child: BookListPage(), )搜索框里onChanged只做一件事:controller.keyword = value。分类筛选条只做一件事:controller.selectedCategory = category。网格列表只做一件事:监听controller.books变化。
6.3 父子组件通信:用回调还是用 Controller?
父子组件通信我严格执行一个原则:只有真正“只影响这个子组件自身”的状态才用本地 State,只要影响兄弟组件或父级就用 Controller。比如BookCard里的收藏按钮,它切换isFavorite后会让分类“收藏夹”下的列表增删变化,影响范围超出单个卡片,所以这个回调往上抛到 Controller 处理。而卡片里“是否展开简介”这种纯本地 UI 状态,就留在StatefulWidget内部自己管理,完全不用惊动 Controller。
这个原则看起来死板,实操中非常管用:代码维护时,你不用猜这个状态改完会影响哪些地方。状态绑定范围越小,心智负担越小。
7. 下拉刷新与滚动加载:给列表加一点“活”的手感
一个没有下拉刷新、没有滚动加载的列表只能算是半成品。Flutter 里下拉刷新有现成组件,滚动加载需要自己监听。这节讲两个细节:RefreshIndicator 的 OpenHarmony 兼容性,和滚动加载中“分页游标”的设计。
7.1 RefreshIndicator 在 OpenHarmony 上的表现
RefreshIndicator是 Material 组件,OpenHarmony 适配层对它支持得不错,但有两个要注意的点:
- 刷新指示器的颜色要和主题色一致。OpenHarmony 设备的深色模式如果没配好,默认指示器颜色在深色背景上会看不清。
- 嵌套滚动时刷新容易误触。如果你列表外面套了
CustomScrollView或者NestedScrollView,RefreshIndicator的触发阈值要调大,否则用户一滑动就触发刷新。
我只用了标准的GridView加RefreshIndicator,触发逻辑就正常。如果你要搞更复杂的滚动容器,建议先写个小 demo 验证下拉手势和列表滚动的冲突。
7.2 滚动加载的“分页游标”设计
单纯的limit/offset分页在记录变多后会碰到一个会有点让人抓狂的问题:用户在书架中间删了一本书,用offset翻页时数据会跳——本来第二页应该接第一页的末尾,结果第一页少了一条,第二页整体往前挪了一位。
我的做法是改用“游标分页”。不记offset,记“上一次加载的最后一个 id”,下一页查询时用WHERE id < lastId ORDER BY updatedAt DESC LIMIT 20。这样即使中间删了记录,下一页也始终从上一页末尾的正确位置开始接。这个细节不显眼,但在长列表体验上差别是质变的。
Future<List<BookModel>> fetchBooks({String? keyword, String? lastId, required int limit}) async { final query = _db.query('books', where: keyword == null ? null : '(title LIKE ? OR author LIKE ?)', whereArgs: keyword == null ? null : ['%$keyword%', '%$keyword%'], orderBy: 'updatedAt DESC', limit: limit, offset: lastId == null ? 0 : null, ); }这里offset和where lastId我用了两种模式,读起来有点绕。实际实现里我只用游标模式,lastId为 null 就是第一页。
8. OpenHarmony 上架前的适配与 XTS 认证:那些跑分之外的硬性要求
列表页功能做完了,接下来面临一个绕不开的环节——上架前的 XTS 认证。OpenHarmony 生态给自己的应用做兼容性认证时,XTS 是一套自动化测试套件,里面有一部分关注的就是 UI 层行为和系统接口调用。这节讲的适配手段,我在提交前逐一验证过。
8.1 XTS 认证对 Flutter 应用的核心考察点
XTS 认证不是只看功能跑通,它会做权限声明检查、系统接口检查、行为合法性检查。Flutter 应用因为是跨端渲染,XTS 关注的更多是你是否误调用了受限接口、是否在权限未授予时就访问了敏感数据类别。
我踩过的一个具体问题:XTS 会检查应用对外声明了哪些权限,如果声明了相机权限但代码里根本没有用到摄像头,会被判定为“权限滥用”。我的做法是拉出一份最小权限清单,只留下网络和本地存储相关权限,把多声明的全部删掉。
8.2 Platform Channel 和 XTS 的边界关系
如果你在 Flutter 代码里通过MethodChannel调用了 OpenHarmony 原生 API,那这部分代码的合规性也要一起过 XTS。我的书摘导出功能需要访问文件目录,这个走的是dart:io的getApplicationDocumentsDirectory,它在 OpenHarmony 适配层有对应实现,没有直接走原生通道。如果你自己封装了 Platform Channel,记得把权限声明和调用链路完整梳理一遍,XTS 日志里查得很细。
8.3 证书签名和版本号规范
OpenHarmony 应用上架前需要做签名。XTS 认证环节会检查应用签名指纹,所以你要在 DevEco Studio 里配置好调试证书、发布证书。版本号尽量用三段式x.y.z,XTS 对版本号的格式有约定俗成的检查逻辑,别用“V1.0”这种非标写法。
8.4 列表性能在认证中的隐形作用
XTS 虽然不开性能打分,但它的自动化遍历会狂点你的页面。如果列表卡顿、内存膨胀、响应超时,遍历脚本会采集到异常数据。所以列表页的性能优化不只是体验问题,还直接影响认证结果的稳定性。我实测中发现GridView.builder的性能明显优于GridView(非 builder 版本),前者是懒加载子项,后者一次性构建全部。大列表场景下差距是数量级的。
9. 几个低频但高破坏力的问题清单:排查思路与解法收录
这部分属于“知道就是红利”的范畴。有些问题不常见,一出现能把人卡到怀疑人生。我按“症状—原因—解决”的方式收录三个我遇到过的,对你排查会有帮助。
9.1flutter run提示 Dart VM Initializer 错误
热搜词里有人踩到E/flutter (31173): [error:flutter/runtime/dart_vm_initializer.cc(41)] unhand,这个错误在 OpenHarmony 上的高发原因是 Flutter 引擎和 Dart 运行时版本不匹配,通常是 Flutter 适配分支和 OpenHarmony 设备的系统版本不对应。解决方法是升级 OpenHarmony SDK 到 Flutter 适配分支要求的 API Level,或者降级 Flutter 版本。
这个报错最坑的是它不给你具体堆栈,只告诉你“未捕获异常”。我的排查路径是先删掉build目录,重新flutter clean后跑原生工程,确认是不是构建产物缓存污染;如果不是,再检查设备侧是否缺libflutter_engine.so。
9.2 Gradle 依赖冲突在混合构建时的诡异现象
热搜词里的'Could not resolve all task dependencies for configuration ':app:debugcompileclasspath'这种报错,OpenHarmony 场景也可能遇到。原因往往是 OpenHarmony 适配分支的 Flutter 工程同时引用了安卓的 Gradle 依赖和 OpenHarmony 的 HAP 构建链,冲突点在settings.gradle和ohos模块的build.gradle里。
处理思路有两种:一是切换到 OpenHarmony 的独立构建入口,避免和安卓模块混编;二是手动排除冲突的传递依赖。我遇到的具体情况是event包版本冲突,加一行configurations.all的 resolutionStrategy 就解决了。
9.3 PlatformView 在列表中的渲染层级问题
如果你的书籍列表里嵌入了PlatformView(比如某个原生组件,像地图或系统播放器),OpenHarmony 上它和其他 Flutter 组件的叠加顺序容易出现诡异遮挡。解决方案是给 PlatformView 设置initialPlatfomView的尺寸后,再用RepaintBoundary包裹,强制渲染层拆分。若还不能解决,调整PlatformViewLink的viewType创建时机,把它延到页面布局稳定后再创建。
这部分问题清单我以后会持续追加,谈不上穷尽,但每一个都是我花了真金白银的时间踩出来的。
10. 回归总结与下一步的扩展思路
书写到这里,核心链路已经完整:从选型、环境、数据层、UI 层、交互逻辑、组件通信、认证适配到疑难排查,一整条“在 OpenHarmony 上用 Flutter 实现书籍列表”的经验脉络,应该能给你省下不少自己摸黑的时间。
最后的扩展方向上,我自己近期在做两件事:一是把书目数据从 SQLite 平滑迁移到更偏文档型的存储,方便跨端同步;二是给列表页做手滑切换“列表/网格”两种视图模式的动画过渡,这个适合 Flutter 的AnimatedSwitcher来实现。如果你在 OpenHarmony 上把这篇里的功能推通了,大概率也能顺畅地加上这些进阶特性。
等你做完自己的表格或书架,再来和我交流实现细节,那些坑才是这个领域最值钱的东西。