ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter鸿蒙手账贴纸App实战:从状态管理到平台适配

Flutter鸿蒙手账贴纸App实战:从状态管理到平台适配 1. 内容整体设计与思路拆解1.1 这个项目到底在做什么先说清楚一句话这是一个用 Flutter 开发、运行在鸿蒙系统上的手账贴纸收藏应用。它不是简单的“图片浏览 App”而是把贴纸管理、收藏、分类、预览和导出这几个核心需求打包在一起的小工具专门给做手账、做电子日记、做拼贴画的人用。为什么选 Flutter因为 Flutter 本身是跨平台框架写一套 Dart 代码可以编译到 Android、iOS、Windows、macOS、Linux现在又加入了鸿蒙生态。对于个人开发者或者小团队来说与其分别为鸿蒙写一套原生代码不如直接用 Flutter 做一套后续如果需要发布到其他平台也能复用大部分逻辑。而“鸿蒙”这个关键词意味着目标设备是鸿蒙系统的手机或平板也就是我们常说的 HarmonyOS 设备。从热词里可以看到很多人关心“Flutter 鸿蒙适配”“flutter platformview”“flutter eventchannel”“flutter 组件通信”“安卓原生项目嵌入 flutter 页面”这类问题这说明在实际开发中大家遇到的难点并不是写 UI 本身而是 Flutter 与鸿蒙原生层的交互、插件适配、状态管理和页面路由。所以这篇教程里我会把这些坑都覆盖到。1.2 为什么适合用手账贴纸这种场景做教学案例很多人学 Flutter 都是从 ToDo List 或者计数器开始的这类例子能学会语法但学不会“真实项目怎么组织”。手账贴纸收藏应用虽然不算复杂但它天然包含了一个实用 App 该有的核心模块贴纸库数据模型贴纸的名称、分类、图片路径、标签、收藏时间列表与网格展示网格布局、缩略图、加载状态收藏与取消收藏数据的增删改查分类筛选按风格、色系、用途等维度过滤贴纸预览与导出分享预览大图、保存到相册本地持久化用数据库或文件存储保存用户数据这些模块覆盖了 Flutter 开发中最常见的技术点布局、状态管理、异步、数据库、平台通道。当你把这些串联起来做成一个完整应用你对 Flutter 工程结构的理解会远超做十几个小 Demo 的效果。而且贴纸收藏应用本身有很强的视觉属性做出来成就感高也容易截图展示适合当成个人作品集项目。1.3 目标读者与前置技能要求这篇教程适合两类人第一类是已经会基础 Flutter 语法知道 Widget、StatefulWidget、ListView、async/await 是怎么回事但没做过完整项目的开发者。你可以通过这个项目把碎片化知识串起来。第二类是从安卓/iOS 原生转 Flutter 的开发者想了解鸿蒙上跑 Flutter 有哪些特殊适配问题。你会看到 EventChannel、MethodChannel 在鸿蒙上怎么用也会看到为什么要用数据库而不是直接存 JSON 文件。如果你完全没接触过 Flutter建议先花一两天跑通官方入门教程了解 Widget 树和 Dart 语法基础再回来。这不是劝退而是为了让你跟得上后面的实操节奏。2. 核心需求拆解与方案选型2.1 功能清单与优先级划分动手写代码前先把需求从“脑子里一闪而过的想法”拆成“能落地的功能列表”。我按优先级分了三档优先级功能模块说明P0必须有贴纸展示网格、收藏/取消收藏、本地存储没有这些就不叫贴纸收藏 AppP1体验加分分类筛选、搜索、贴纸预览让 App 好用的关键P2进阶导出分享、批量操作、云同步后期可以迭代的方向P0 是骨架子P1 是血肉P2 是装饰。我建议先把 P0 和 P1 做完P2 根据精力再决定。2.2 状态管理方案选择Flutter 的状态管理方案有很多setState、Provider、Riverpod、Bloc、GetX 等。这里我选的是Riverpod也就是热词里“flutter cubit”背后的竞品思路。为什么不用 setState因为它适合极小的组件内部状态一旦跨页面共享比如贴纸收藏状态要在列表页和详情页同步你就得在父组件层层传回调代码会变得很啰嗦。为什么不用 BlocBloc 非常适合大型团队项目但它的样板代码太多了做一个贴纸收藏应用会被各种 Event/State 类淹没。Riverpod 的 API 简洁支持依赖注入最重要的是它在组件重建时能自动管理状态生命周期很适合中等复杂度项目。顺便解释一下热词里提到的“flutter cubit”。Cubit 是 Bloc 库的精简版它把 Stream 封装成了简单的方法调用。如果你一开始就选了 Bloc 家族用 Cubit 写贴纸收藏逻辑会比完整 Bloc 轻量得多。但如果你还没定方案Riverpod 的上手曲线更平滑。2.3 本地存储方案选择sqflite 还是 shared_preferences贴纸收藏应用的数据结构其实不止“收藏了哪些贴纸”这么简单。你需要存贴纸本身的元数据名称、路径、标签、创建时间还要存用户的收藏时间、排序方式。如果用 shared_preferences 把整个列表序列化成 JSON 字符串数据量小的时候没问题但一旦贴纸超过几百张每次读写全量数据会卡顿而且容易出现并发写入的竞态问题。所以我选sqflite也就是 SQLite 数据库。SQLite 轻量、稳定、适合存结构化数据而且 Flutter 的 sqflite 插件在鸿蒙生态里也有适配版本。数据库的表结构设计我放在第 3 部分细说这里先给结论结构化数据用数据库模板信息用 shared_preferences。顺带提一下热词里的同类型替代品我们用过 SQLite 之后别把它跟桌面端“db4s:一个开源跨平台的sqlite数据库管理工具”搞混。db4s 是用来可视化编辑数据库文件的桌面工具开发调试时确实好用我后面会提到怎么用它检查数据库里的数据是否正确。2.4 图片资源管理的坑贴纸应用最核心的资产是贴纸图片。这里我强烈建议不要把贴纸图片都塞进 Flutter 的 assets 目录而是用文件系统存储数据库只保存路径。原因有三个第一assets 打包进安装包会让 APK/HAP 体积爆炸。200 张贴纸每张 1MB安装包就多了 200MB这不可接受。第二assets 在运行时只能读不能写功能上就限制了“用户自定义贴纸”的扩展空间。第三用文件系统存储后续可以做“从网上下载贴纸包”的功能而 assets 做不到。具体做法App 首次启动时把内置的默认贴纸资源从 assets 复制到应用的私有目录然后向数据库插入对应的元数据记录。之后所有读写都走文件系统路径。3. 环境准备与工程搭建3.1 鸿蒙开发环境的坑位说明在开始之前先把鸿蒙开发相关的环境说清楚。鸿蒙开发并不仅仅指“HarmonyOS 的 ArkTS 原生开发”还包括“在鸿蒙设备上运行 Flutter 应用”。所以你需要两套东西一套是 Flutter SDK建议用 3.16 以上版本因为从某个版本开始 Flutter 官方社区版才稳定支持鸿蒙相关特性。具体要看你自己机器上装的 Flutter 版本低于 3.10 的话很多鸿蒙适配插件会报错。另一套是鸿蒙 SDK 和 DevEco Studio。注意鸿蒙应用的打包产物是 HAP 文件和安卓的 APK 不同。直接编译 Flutter 工程时你需要安装 flutter 的鸿蒙 SDK 支持包也就是flutter_fluttter相关的项目。这个项目是 Flutter 官方团队和 OpenHarmony 社区合作的成果它会生成鸿蒙版 Flutter 引擎让你的 Flutter 代码跑在鸿蒙运行时上。这部分的实操细节网上教程混杂我提醒几个容易踩的坑不要试图用纯 Android SDK 直接编鸿蒙 HAPFlutter 构建系统会尝试用 Gradle 编译安卓产物但鸿蒙需要的是 Hvigor 构建系统两者的工程目录结构和工具链不同。鸿蒙模拟器的性能和真机相差很大涉及图片渲染、路由动画这类效果尽量用真机测试。如果你遇到“you are applying flutters main gradle plugin imperatively using the apply method”这种报错说明你的 Flutter 工程还在用老的 Gradle 配置方式需要改成新的插件声明式配置。3.2 创建 Flutter 项目并加入鸿蒙支持我假设你已经在电脑上安装好了 Flutter、Dart 和 DevEco Studio。步骤如下第一创建 Flutter 项目flutter create sticker_album cd sticker_album这里注意项目名不要用大写字母Dart 包名规范要求小写加下划线。第二添加鸿蒙支持。目前社区提供的支持方式是通过flutter_fluttter工具或者从鸿蒙开源仓库拉取模板。最省事的方式是直接下载 OpenHarmony 的 flutter 模板工程然后把你的lib目录和pubspec.yaml覆盖进去。这个操作听起来有点绕但实际做过一次就明白了本质上是借用官方已经配好 Hvigor 和鸿蒙依赖的壳工程来承载你的 Flutter 模块。第三在pubspec.yaml里添加依赖dependencies: flutter: sdk: flutter flutter_riverpod: ^2.4.0 sqflite: ^2.3.0 sqflite_common_ffi: ^2.3.0 path: ^1.8.3 path_provider: ^2.1.1 shared_preferences: ^2.2.1其中sqflite_common_ffi是为了在桌面端调试数据库时用的不是必须但推荐加。第四跑起来之前先检查 Flutter 版本flutter --version如果是 3.10 以下的版本建议先升级。热词里有人搜“flutter windows3.47.5下载”其实那是 Flutter 3.7.5 的笔误还是什么不重要你只要保证 Flutter 版本不落后太多就行。第五构建鸿蒙 HAP 的时候运行的不是flutter build apk而是需要先构建 Flutter 的产出包再通过 DevEco Studio 加载鸿蒙工程完成 HAP 编译。具体命令在鸿蒙 flutter 模板的 README 里有通常长这样flutter build hap --debug如果构建报错大概率是鸿蒙 SDK 路径没配好去环境变量里检查DEVECO_SDK_HOME有没有指向 DevEco Studio 的 SDK 目录。3.3 适配后端走一遍从热词里看到了一个高频提问“安卓原生项目嵌入 flutter 页面”以及“flutter platformview”。这说明很多人其实是在做混合开发也就是在已有的鸿蒙/安卓原生 App 里嵌入 Flutter 页面。如果你属于这种情况工程结构会略有不同原生工程作为宿主Flutter 作为 module 接入。鸿蒙端接入 Flutter 的 module 时需要配置 Ability 的跳转也就是在鸿蒙的module.json5里声明 Flutter 页面对应的 Ability。Flutter 和原生通信要用 Platform Channel鸿蒙端的实现类需要继承PlatformChannel并实现对应方法。这个流程比较繁琐但核心思想不变Flutter 页面本质上还是在原生壳里渲染的视图它的生命周期受原生管理所以原生切后台、切前台时Flutter 的状态恢复需要额外处理。这里不展开纯原生嵌入的细节后面第 5 部分讲 EventChannel 时会以鸿蒙原生侧的角度再讲一次。4. 手账贴纸收藏应用的核心实现4.1 数据表设计不要只建一张表我给你看一张我实际用的表结构CREATE TABLE stickers ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, file_path TEXT NOT NULL, category TEXT DEFAULT 未分类, tags TEXT DEFAULT , is_favorite INTEGER DEFAULT 0, created_at INTEGER NOT NULL );为什么用file_path不直接存图片二进制因为 SQLite 存 BLOB 会导致数据库文件巨大而且读取时会在内存里产生大字节数组。存路径的话图片解码交给 Flutter 的Imagewidget 去做这样更顺滑。tags字段我用的是逗号分隔字符串比如“复古,拼贴,花卉”。虽然规范化角度应该另建tags表做多对多但在贴纸收藏这种轻量级场景里一个字段存逗号分隔足够查询时用LIKE就能筛选。再建一个收藏夹表不需要我直接用is_favorite字段。只有当你需要多个收藏夹、支持贴纸重复出现在不同收藏夹时才需要单独的表。4.2 数据库初始化与 DAO 模式在 Flutter 里使用 sqflite 的老套路是写一堆裸 SQL 字符串然后到处调用db.query。项目小没事涉及跨页面复用时会很痛苦。所以我建了StickerDao类专门管理数据库读写。初始化数据库的代码长这样简化版class AppDatabase { static Database? _db; static FutureDatabase get instance async { _db ?? await _initDatabase(); return _db!; } static FutureDatabase _initDatabase() async { final dir await getApplicationDocumentsDirectory(); final path p.join(dir.path, sticker_album.db); return openDatabase( path, version: 1, onCreate: (db, version) async { await db.execute(CREATE TABLE stickers (...)); }, ); } }需要注意一点getApplicationDocumentsDirectory在鸿蒙上同样可用因为它由path_provider的鸿蒙适配提供。如果没有适配你就得通过 MethodChannel 自己拿路径这也是很多人卡住的地方。幸好现在主流插件的鸿蒙支持已经比较成熟直接依赖官方适配版本就行。StickerDao里我封装的典型方法FutureListSticker getAllStickers()FutureListSticker getFavoriteStickers()FutureListSticker getStickersByCategory(String category)Futurevoid updateFavorite(int id, bool isFavorite)这些方法内部用 sqflite 的query、update完成代码不复杂但把 SQL 细节藏起来UI 层调用会清爽很多。4.3 贴纸展示网格的设计细节网格布局用GridView.builder每个 item 是一个卡片包含贴纸图片、名称、分类标签、收藏心形图标。这里有几个设计点第一图片加载用Image.file(File(path))因为你存的路径是本地路径不是 asset。如果你把 asset 路径当作 file_path 存那使用Image.file会报找不到文件要用Image.asset。所以确定存储策略后别混用。第二GridView的childAspectRatio控制卡片宽高比。手账贴纸一般是方形所以我设为 0.9 左右给底部文字留出空间。不过鸿蒙上不同屏幕密度差异比较大建议用SliverGridDelegateWithMaxCrossAxisExtent代替固定列数GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 0.85, ), itemCount: stickers.length, itemBuilder: (context, index) StickerCard(sticker: stickers[index]), )这样平板大屏幕上会自动多列手机上保持在两到三列。第三收藏心形图标必须触感明显。我用了IconButton点击时调用StickerDao.updateFavorite同时更新本地状态。为了避免疯狂点击造成数据库压力可以在onPressed里加一层节流比如 300 毫秒内不允许重复点击。4.4 状态管理实现Riverpod 的使用姿势我定义了一个StickerListProvider它负责加载数据库里的贴纸数据并提供切换收藏状态的方法final stickerListProvider StateNotifierProviderStickerListNotifier, ListSticker((ref) { return StickerListNotifier(); }); class StickerListNotifier extends StateNotifierListSticker { StickerListNotifier() : super([]) { loadStickers(); } Futurevoid loadStickers() async { final stickers await StickerDao().getAllStickers(); state stickers; } Futurevoid toggleFavorite(int id) async { final index state.indexWhere((s) s.id id); if (index -1) return; final sticker state[index]; final updated sticker.copyWith(isFavorite: !sticker.isFavorite); state [...state]..[index] updated; await StickerDao().updateFavorite(id, updated.isFavorite); } }注意这里我先改了内存状态再异步更新数据库。这样 UI 响应快不会等数据库操作完成才刷新。如果数据库更新失败再用 SnackBar 提示用户。有人担心这样会造成界面和数据库不一致只要数据库操作足够可靠这个方案是能接受的。从热词里看到有人搜“flutter future的then回调 是放入微任务队列吗”我顺便科普一下Future.then的回调确实会进入微任务队列这意味着它在当前事件循环的同步代码执行完之后立刻执行不会等待新的 UI frame。上面代码里先改state触发重建然后执行异步数据库操作这个顺序是合理的。如果你在then里又去改 state一定要预估到多次重建的代价。4.5 分类筛选与搜索分类入口我放在顶部用ChoiceChip做横向滚动列表SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: categories.map((c) { return Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: ChoiceChip( label: Text(c), selected: selectedCategory c, onSelected: (_) ref.read(stickerListProvider.notifier).filterByCategory(c), ), ); }).toList(), ), )要注意筛选逻辑放在 Notifier 里还是 UI 层。我的建议是放 Notifier因为分类状态本身是共享的如果多个页面都要用分类信息放 UI 局部变量会导致状态不同步。搜索框我用TextFieldonChanged节流。最简单的方式是debounce300 毫秒后触发过滤。你可以手动写一个 Timer也可以直接用StreamTransformer。这里我手写Timer? _debounce; void onSearchChanged(String query) { _debounce?.cancel(); _debounce Timer(Duration(milliseconds: 300), () { ref.read(stickerListProvider.notifier).search(query); }); }记得在dispose里取消 Timer否则页面销毁后还可能执行一次过滤逻辑触发已销毁的 state。4.6 贴纸预览与保存到相册点击贴纸卡片跳转详情页页面里放大展示贴纸底部有“收藏”和“保存到相册”按钮。保存到相册这一块Flutter 官方没有统一 API安卓上有image_gallery_saver这类插件鸿蒙上则需要验证兼容性。如果你的目标主要是鸿蒙设备建议干脆用 MethodChannel 自己实现一个保存图片到相册的原生方法。具体实现思路Flutter 侧拿到图片文件路径把文件二进制读取出来作为 Uint8List。调用MethodChannel(samples.sticker/save)参数传字节数组。鸿蒙侧接收字节数组调用媒体库的接口写入相册。这个流程涉及平台通道我会在下一部分专门讲。这里先留个钩子。5. 鸿蒙平台适配与原生交互5.1 Flutter 与鸿蒙原生通信的两种姿势热词里高频出现“flutter eventchannel”这次直接说清楚。Flutter 与原生平台通信有三种 ChannelMethodChannel、EventChannel、BasicMessageChannel。MethodChannel是最常用的适合“你问一句我答一句”。比如 Flutter 调用原生保存图片原生返回保存结果这就是 MethodChannel。EventChannel适合原生向 Flutter 持续推送事件。比如监听系统音量变化、网络状态、充电状态等。你在贴纸应用里可以用它监听相册图片是否发生了变化以便自动发现新素材并加入贴纸库。BasicMessageChannel适合传递较为复杂的数据格式类似 MethodChannel 但更通用可以双向发送。在鸿蒙端实现 MethodChannel 时你需要创建一个PlatformChannel子类注册在 MainAbility 或需要在相应 Ability 里注册。下面我给一个简化版的思路// 鸿蒙侧代码ArkTS 或 Java import { MethodChannel } from ohos/flutter_ohos; export class SaveImageChannel extends MethodChannel { constructor() { super(samples.sticker/save, StandardMethodCodec.INSTANCE); } handleMethodCall(method: string, args: any, result: MethodResult) { if (method saveImageToGallery) { // 调用媒体库接口保存 args.imageBytes result.success(true); } else { result.notImplemented(); } } }注意鸿蒙的媒体库接口在高版本 API 和低版本 API 之间存在差异如果是老版本 API需要申请存储权限在新版本模块上使用安全控件或者特定的媒体库权限。这个需要查阅当前鸿蒙 SDK 的ohos.multimedia.mediaLibrary文档。由于 HarmonyOS 版本一直在迭代我不在这里贴具体 API以免误导。原则就是能在 Native 层做的事就不要在 Flutter 层用 JavaFile硬写因为鸿蒙的文件系统与安卓有差异。5.2 EventChannel 在贴纸应用里的实用场景我在这个项目里用 EventChannel 做了一件事监听系统相册新增图片。当前手账用户经常会往手机里存各种图片素材如果 App 能自动检测到系统相册新增了图片就可以提示“发现新素材是否加入贴纸库”这个体验很加分。实现流程是Flutter 侧创建EventChannel(sticker/galleryObserver)。receiveBroadcastStream().listen((event) { ... })收到事件后刷新贴纸列表或弹出提示。鸿蒙侧注册一个 EventChannel内部添加相册内容观察者每次有新增图片时把图片路径发到 Flutter。注意 EventChannel 的流生命周期与 Widget 无关页面销毁后事件依然会被发送。所以要在dispose里调用cancel()或者用一个全局订阅管理器。如果忘记取消轻则内存泄漏重则导致重复刷新、二次弹窗。5.3 鸿蒙 PC 版和开源鸿蒙的联想热词里有人搜“开源鸿蒙pc版官网下载”“鸿蒙系统pc版官网”这些都是鸿蒙生态当前的热度但和手账贴纸应用开发没有直接关系。你只需要知道Flutter 的跨平台能力未来也许能覆盖鸿蒙 PC 端但目前主流还是手机和平板。开发时依然按照移动端适配逻辑来处理就对了。另外不要相信什么“鸿蒙 fc”“鸿蒙 6.0”这些营销词开发环境看 DevEco Studio 的版本和 SDK 版本就够。如果你的 Flutter 和鸿蒙适配插件版本太新或太旧都有可能跑不起来最好的策略是“以你的 DevEco Studio 自带的 SDK 版本为准选择适配该版本的 Flutter 鸿蒙插件”。6. 常见问题与排查技巧实录这一部分整理我在实际开发和帮别人排查时碰到的高频问题做成速查表方便你直接对照解决。6.1 构建报错速查表报错线索实际原因解决思路Could not close input stream多半是 Gradle 下载依赖资源失败或者文件锁冲突清理 Gralde 缓存重启 IDE 再试You are applying flutters main gradle plugin imperatively using the apply methodFlutter Gradle 插件配置方式过旧在android/settings.gradle中改用 plugins DSL 声明不要用手动 applyundefined symbol或linker command failed鸿蒙原生插件与 Flutter 引擎版本不匹配检查插件版本统一使用 OpenHarmony 适配分支构建产物是 APK 而不是 HAP没有引入鸿蒙构建配置实际开发时要走 Flutter 鸿蒙模板构建流程不能用 Flutter 默认的 android 构建Kotlin 版本冲突原生插件依赖的 Kotlin 与 Flutter 默认 Kotlin 版本不一致在build.gradle里统一 Kotlin 版本或者移除冲突依赖Column 1 的Could not close input stream是很多人遇到的经典报错一般发生在打包阶段。它通常不是代码问题而是 Gradle 的旧缓存和新依赖产生冲突。直接执行flutter clean然后删除android/.gradle和用户目录/.gradle/caches再重新构建很大概率能解决。如果还是不行检查是不是 JDK 版本太高部分 Flutter 版本不支持 JDK 21建议用 JDK 17 或 11。6.2 Flutter 页面状态丢失的坑热词里有人问“flutter navigator切换页面后,会丢失状态吗”。这是一个经典问题。在Navigator.push跳到新页面后前一个页面的 State 默认不会销毁它保留在路由栈里。但是如果你用了GridView.builder并滚动了一段距离返回时可能会发现滚动位置变了这通常是 PageStorage 的锅。解决办法使用CustomScrollView并给ScrollController添加 key。或者把滚动位置保存在PageStorageKey中。例如GridView.builder( key: PageStorageKey(sticker_grid_$category), ... )这样返回时滚动位置会恢复。还有一种“状态丢失”是因为你在页面退出时调用了ref.invalidate(provider)或者是Scaffold被全局 unique 约束了。比如MaterialApp的home页面用的是同一个GlobalKey导致切换 Tab 时整个子树重建所有 State 都会丢。检查你是不是在多个页面错误使用了同一个GlobalKey这也是高频坑。6.3 数据库升级与崩溃如果你的贴纸应用后续要加字段比如给贴纸加“使用次数”数据库版本升级就是必须面对的。直接删掉 App 重装会把用户收藏数据清空这显然不行。sqflite 的升级方式是在openDatabase的onUpgrade回调里执行ALTER TABLEreturn openDatabase( path, version: 2, onCreate: (db, version) async { ... }, onUpgrade: (db, oldVersion, newVersion) async { if (oldVersion 2) { await db.execute(ALTER TABLE stickers ADD COLUMN usage_count INTEGER DEFAULT 0); } }, );如果你改了表结构但是忘记更新 version就会出现数据库字段缺失、查询崩溃的现象。检查这类问题不用在手机上反复调试可以把数据库文件导出到电脑上用 db4s 打开看看实际表结构一目了然。这也是我从“db4s:一个开源跨平台的sqlite数据库管理工具”这个工具里获得的好处。6.4 图片加载失败与 OOM贴纸应用加载大量本地图片如果直接Image.file(File(path))且图片分辨率超高很容易把内存吃满。特别是鸿蒙平板屏幕大GridView会一次性加载很多可见 item内存峰值很高。解决办法是给图片配置缓存宽度Image.file( File(path), width: 200, height: 200, cacheWidth: 200, fit: BoxFit.cover, )cacheWidth会强制解码为宽度 200px 的位图大量降低内存占用。这个参数一定要加否则图片原尺寸是 2000px解码出来占的内存就是 0.2MB 原图变成 16MB 位图两三百张排队加载必 OOM。另外不要在GridView.builder的 itemBuilder 里同步读取文件来做滤镜、旋转等复杂操作那会阻塞 UI。有需要就在 isolate 里处理或者用compute。6.5 关于状态保持的一个常见迷思热词里有“flutter future的then回调 是放入微任务队列吗”这其实涉及 Dart 的事件循环。简单说Dart 中Future的回调确实会被放进微任务队列在下一个事件如新的 UI frame、输入事件之前执行。这与Timer.run放进事件队列不同。如果你在这个项目里遇到“UI 明明调用了 setState但迟迟不刷新”可能是因为你在微任务里大量执行同步计算阻塞了 UI 渲染。解决办法是把耗时操作放到compute或者用Future(() async {...})将任务推迟到事件队列。7. 后续扩展与个人心得7.1 从“能用”到“好用”的三个优化方向第一个方向是批量管理。目前点收藏只影响单个贴纸如果用户有一百张贴纸要批量打标签没有选择模式会疯。你需要加长按多选、顶部操作栏、批量修改功能的实现。这个功能在 Flutter 里不难但会触及SelectionArea和手势冲突是很好的进阶练习。第二个方向是贴纸包导入导出。用 JSON 格式把贴纸的元数据和图片字节打包成 zip支持用户之间分享贴纸包。看起来高大上其实核心就是文件压缩和 JSON 序列化技术难度不高。第三个方向是云同步。如果你打算让用户在不同设备上同步收藏就要研究账号体系和云端存储方案。注意这涉及到隐私和数据合规不要直接把用户图片传到不靠谱的服务器。不过从技术角度用 Firebase 或者华为的云服务都可以但这已经远超这个教程的范畴了。7.2 我在这个项目上踩过的最大的坑说一个我印象最深的坑一开始我把贴纸图片放在 assets 目录然后通过rootBundle.load读取字节再写入到getApplicationDocumentsDirectory。结果是每次启动 App 都会复制一遍几百MB图片不仅启动卡死存储空间也被吞噬。后来我改成“只复制一次”的模式在数据库里存一个seed_version字段每次启动检查当前版本号如果版本号不变就不复制。这个方案有效但对后续更新内置贴纸包不够友好。最后我干脆只预置四五个示例贴纸其余全部让用户自己从本地导入这样既保持了安装包小又绕开了默认资源的更新问题。这件事让我明白一个道理跨平台框架虽然方便但平台特性差异永远无法完全消除。尤其是鸿蒙这类较新的生态插件的成熟度比不上安卓很多问题要靠自己绕路。如果你想做一个真正面向鸿蒙用户的应用一定要拥抱原生层而不是把所有逻辑都写在 Flutter 里。Flutter 解决 UI 和业务流原生解决系统能力两者结合才是最优解。7.3 给新手的最后提醒如果你是第一次跑通全流程不要一上来就追求复杂设计。先把“显示贴纸 - 收藏 - 保存到数据库”这个最小闭环跑通然后再加分类、搜索、预览等功能。你也可以先从安卓模拟器跑通逻辑再切换到鸿蒙工程验证适配问题这样调试效率更高。另外我强烈建议你把每一步都提交到 Git。这个项目的代码量不大但涉及平台配置的改动非常多。当你在鸿蒙构建配置中搞乱了某个文件有 Git 回退会省去很多痛苦。最后再分享一个小技巧调试数据库时别在代码里打一堆 print直接在应用里做一个隐藏的“导出数据库”按钮通过 share 把 db 文件发到电脑上用 db4s 打开查看。你就能直观看到每张表的字段值找 bug 效率翻倍。这个项目做完之后你会同时理解 Flutter 的跨平台能力、状态管理、数据库设计、平台通道、鸿蒙工程结构这些知识足够支撑你继续去做更复杂的产品。剩下的就是多写、多踩坑、多反思。
返回列表