首页 / 资讯中心 / 文章详情

Flutter在OpenHarmony上实现个人理财交易详情页的完整实践

Flutter在OpenHarmony上实现个人理财交易详情页的完整实践 ★ FEATURED ARTICLE
做个人理财类 App最绕不开的一个页面就是交易详情页。它不像首页那样要堆很多运营位也不像图表页那样追求炫酷的动效但它恰恰是用户打开频率最高、最容易盯着看半天的页面。用户记账之后所有的信任感都建立在这个页面上这笔钱花在哪了、什么时候花的、花得值不值。这次我用 Flutter 在 OpenHarmony 设备上完整实现了这个页面。选择这个组合的原因也很直接OpenHarmony 的分布式能力和多设备协同是亮点而 Flutter 的跨端渲染能力刚好能让我一套代码同时覆盖手机、平板和带屏设备。本文就把从页面结构设计、数据模型建立到图表绘制落地的全过程拆开来讲重点讲那些踩过坑之后才搞明白的细节。1. 页面效果与信息架构拆解交易详情页的核心任务不是“展示数据”而是“建立信任”。用户打开这个页面时心里通常带着三个问题这笔钱确实验证无误吗这笔钱花在什么地方了相比同类消费这笔钱是否合理1.1 需求分析与页面层级设计整个页面我分成三个视觉层级第一层是金额展示区占据页面顶部约 35% 的空间。这里用大号字体展示交易金额收入用暖色调、支出用冷色调让用户第一眼就能判断资金方向。第二层是交易信息区展示分类、账户、时间、备注、交易单号等结构化字段。第三层是统计与操作区展示该分类下的月度消费占比并提供删除、编辑、申诉等操作入口。层级越靠上越需要“轻”——用户一进来只需要看到结果不需要被一堆按钮干扰。很多理财 App 的详情页做得太“重”上来就是十几个字段平铺用户反而找不到重点。我在设计时坚持了“一个页面只回答一个问题”的原则顶部回答“这笔钱是什么”中部回答“这笔钱从哪里来、到哪里去”底部才引导用户进行下一步操作。1.2 视觉风格与组件选型思路OpenHarmony 设备上跑 Flutter视觉风格需要考虑系统差异。HarmonyOS 的设计语言偏向圆润、轻量阴影层次丰富而 Flutter 默认的 Material 风格偏平、偏硬。我的处理方式是放弃 Material 的默认 AppBar改用自绘头部卡片圆角统一设置为 16dp阴影用多层渐变模拟而不是直接用 elevation。组件选型方面图表部分没有直接引入第三方库而是用 CustomPaint 自绘了一个环形进度条。原因有两点一是 OpenHarmony 生态下的 Flutter 三方库适配情况参差不齐很多库在标准 Flutter 上跑得好好的换到 OpenHarmony 就出现纹理异常或字体渲染问题二是交易详情页的图表形态非常简单——一个环形占比图加几行图例文字自绘不到 100 行代码就能搞定引入一个几百 KB 的图表库反而得不偿失。2. 数据模型设计与状态管理数据模型是整个页面的地基。我见过太多项目先写 UI 再补数据层最后 UI 被数据模型牵着走改数据模型就等于改页面。交易详情页涉及的数据结构不复杂但字段之间的关联逻辑需要提前理清尤其是交易状态、账单周期、分类归属这几个维度的交叉组合。2.1 交易记录模型的字段定义我定义了一个 TransactionRecord 模型类包含以下核心字段class TransactionRecord { final String id; // 交易唯一标识 final String title; // 交易标题 final double amount; // 交易金额支出为负、收入为正 final int categoryId; // 分类ID关联分类表 final String accountId; // 账户ID final DateTime occurredAt; // 实际发生时间 final String remark; // 用户备注 final TransactionStatus status; // 交易状态正常/已删除/已申诉 final bool isExpense; // 收入还是支出冗余字段避免到处判断 }这里有两个反直觉的设计。第一我故意把 isExpense 单独拆成一个布尔字段而不是通过 amount 正负来判断。原因是后续要做筛选和统计直接用布尔字段走索引比在代码里做正负判断快得多而且语义更明确第二status 字段看起来冗余——正常情况下一笔交易不是正常就是删除但实际场景里“已申诉”状态非常必要比如用户发现某笔重复扣款需要在详情页发起申诉这个状态下交易仍要展示但不可以编辑。2.2 Provider 状态管理与页面刷新策略状态管理我选了 Provider 而不是 Bloc。不是说 Bloc 不好而是对于交易详情这个场景状态变更的触发源非常集中——用户进来时加载一次数据编辑后刷新一次删除后返回上一页。这种粒度的状态管理用 Provider 的 ChangeNotifier 就够了引入 Bloc 反而要写一堆 Event、State、Bloc 三个文件的样板代码。刷新策略上有两个细节值得讲页面从列表页进入时携带 transactionId详情页的 Provider 根据 ID 去拉取数据。这里要注意不要直接传递整个交易对象因为列表页的数据可能已经过期比如用户在其他设备上修改过这笔交易。传递 ID 让详情页重新查询保证数据一致性。编辑操作完成后利用 Flutter 的Navigator.pop返回值回传更新标志。详情页在then回调里判断是否需要重新拉取数据。这样避免每次从编辑页返回都做无谓的网络请求。final result await Navigator.push( context, MaterialPageRoute(builder: (_) EditTransactionPage(recordId: widget.recordId)), ); if (result true) { _loadTransactionDetail(); }2.3 模拟数据与真实数据的平滑切换开发阶段我用了一个 MockDataSource 来模拟网络延迟和随机数据生成上线后切换到真实接口。关键在于接口签名要保持一致两个数据源都实现同一个TransactionRepository抽象类abstract class TransactionRepository { FutureTransactionRecord fetchById(String id); FutureListCategoryStatistic fetchCategoryStatistics(int categoryId, DateTime month); }抽象类的好处是UI 层只依赖 repository 接口不关心数据到底是从本地数据库来还是从远程 API 来。测试时注入 Mock 实现上线时替换为 Dio 网络实现整个页面的逻辑不用改动一行。3. 核心 UI 组件实现与适配经验这一节是实际操作中最耗时、也最容易出问题的部分。Flutter 开发者的直觉是“UI 就是搭积木”但在 OpenHarmony 设备上这套积木并不总是严丝合缝。以下几个方面是我反复调整后稳定下来的实现方案。3.1 分类图标与会徽设计的本地化适配交易分类图标是详情页的点睛之笔。每个分类对应一个图标——餐饮是餐具、购物是购物袋、交通是汽车、医疗是十字架。我的实现方案是用IconData组合先用Container画一个圆形背景再叠加图标字体。但这里有个坑Flutter 自带的 Material Icons 字体在 OpenHarmony 系统上存在字体缺失问题部分生僻字形显示为豆腐块。我在开发中先是发现“转账”分类的图标显示异常排查半天发现是字体回退策略的问题。解决方案是改用自定义 SVG 图标作为分类图标不依赖系统字体。具体做法是用flutter_svg库加载本地 SVG 资源分类图标统一放在assets/icons/category/目录下。如果项目里有大量图标需要处理还可以用一个简单的脚本把 SVG 批量转换为 Flutter 的Path对象写入一个CategoryIcons静态类里class CategoryIcons { static const Path diningPath Path(); // 实际为转换后的路径数据 // ... }这样既解决了字体缺失问题又能保证不同设备上渲染一致。需要提醒的是SVG 转 Path 之后部分复杂的渐变和滤镜特效会丢失所以图标设计阶段就尽量用纯色、简单的几何形状避免后期转换时效果缩水。3.2 自绘环形进度条实现月度预算占比详情页下方的“该分类本月消费占比”区域我选择用自绘的环形进度条来呈现。CustomPaint 的实现代码如下class CategoryRingPainter extends CustomPainter { final double progress; // 0.0 ~ 1.0 final Color trackColor; final Color progressColor; override void paint(Canvas canvas, Size size) { final strokeWidth 14.0; final center Offset(size.width / 2, size.height / 2); final radius (size.width - strokeWidth) / 2; final trackPaint Paint() ..style PaintingStyle.stroke ..strokeWidth strokeWidth ..color trackColor; final progressPaint Paint() ..style PaintingStyle.stroke ..strokeWidth strokeWidth ..strokeCap StrokeCap.round ..color progressColor; canvas.drawCircle(center, radius, trackPaint); final rect Rect.fromCircle(center: center, radius: radius); canvas.drawArc(rect, -pi / 2, 2 * pi * progress, false, progressPaint); } }整个绘制逻辑就是把一个 360 度的圆环根据 progress 值截取相应的弧度。这里有两个细节起点角度必须设成-pi / 2也就是从 12 点钟方向开始画这样视觉上比较自然useCenter参数必须设为 false否则画出来的是一个扇形而非圆环。数据读取方面进度条的值是通过CategoryStatistic聚合数据算出来的progress 本月已消费金额 / 该分类预算总额。如果预算总额是 0就直接显示一个默认的灰色圆环避免除零异常。3.3 交易信息分组的两种列表实现对比交易信息区的字段很多我按区块分成“基本信息”和“更多信息”两组展示。实现上可以选用ListView加Column组合也可以用CustomScrollView加SliverList。最开始我用的是ListView因为实现简单、代码量少。但后来发现当信息字段超过一屏时页面滚动过程中整个列表会整体重建导致滚动卡顿尤其是在低端 OpenHarmony 设备上。后来改用CustomScrollView加SliverList的方案把每个信息区块拆成独立的 Sliver滚动时只重建可见的 Sliver 区域性能提升明显。同时页面顶部的金额展示区可以固定在 SliverAppBar 区域滚动时平滑收缩视觉上更有层次感。关键的代码结构如下CustomScrollView( slivers: [ SliverAppBar( expandedHeight: 180, pinned: true, flexibleSpace: FlexibleSpaceBar( title: _buildAmountHeader(), ), ), SliverPadding( sliver: SliverList( delegate: SliverChildListDelegate(_buildInfoSections()), ), ), ], )另一个值得注意的点条目间隔不要用SizedBox硬编码而是用一个统一的EdgeInsets常量来管理。这样当不同设备屏幕尺寸变化时可以整体调整 padding 值而不是一个个去改 UI。3.4 长备注文本的展开收起与安全区适配交易备注字段是用户自由输入的内容最短可能只有一个字最长可能写一大段。直接全部展示会占用过多版面只展示一行又丢失信息。我的方案是默认展示两行超过两行时高亮显示“展开”按钮点击后完全展开并显示“收起”按钮。实现上不用第三方库用TextPainter提前测量文本高度判断是否需要显示展开按钮final textPainter TextPainter( text: TextSpan(text: remark, style: textStyle), maxLines: 2, textDirection: TextDirection.ltr, )..layout(maxWidth: constraints.maxWidth); final isOverflow textPainter.didExceedMaxLines;OpenHarmony 设备的安全区适配也是一个刚踩过的坑。鸿蒙设备普遍采用圆角屏幕和全面屏手势如果不处理MediaQuery.of(context).padding页面底部的操作按钮会被系统手势区域遮挡。我在页面最外层用SafeArea包裹并在底部操作栏额外加了一个bottomNavigationBar高度补偿确保“删除”和“编辑”按钮在任何设备上都能完整点击。4. Flutter 在 OpenHarmony 上的编译适配问题记录Flutter 版本一直在迭代OpenHarmony 的适配层也在同步演进。我用的组合是 Flutter 3.7 版本接入 OpenHarmony 的 ohos 分支整体跑通花了比较多时间主要卡在环境配置和依赖兼容上。提前了解这些坑能帮你少走不少弯路。4.1 环境搭建中必须踩过的配置细节OpenHarmony 设备上调试 Flutter 应用需要用从 OpenHarmony 官方 SDK 改造过的 Flutter 引擎。普通 Flutter SDK 编译出的应用无法直接跑在 OpenHarmony 设备上。这就意味着开发者本机需要准备两个 Flutter SDK一个用于标准 Android/iOS 开发一个用于 OpenHarmony 开发。切换时要把 PATH 环境变量和 IDE 里的 SDK 路径都改掉很容易搞混。我的建议是直接用 FVMFlutter Version Management来管理多版本 SDK。配置好之后在项目根目录运行fvm use 3.7.0-ohos同时需要把ohos平台的编译配置文件加入项目flutter create --platforms ohos .这个命令会在项目里生成ohos目录和对应的编译配置。如果没有这一步后面flutter build ohos会直接报错找不到平台配置。另外从第三方下载的 Flutter ohos SDK 版本要跟项目里pubspec.yaml声明的 SDK 约束版本匹配不匹配时编译期会出现大量莫名其妙的类型报错。4.2 三方库的兼容性排查方法并不是所有 Flutter 三方库都能在 OpenHarmony 上直接运行。部分库依赖 Android 的 Native 代码在 OpenHarmony 上会报 MissingPluginException部分库依赖 iOS 的 Foundation 框架在 OpenHarmony 上也无法使用。在实际开发中我主要用以下几种手段排查兼容性查阅该库在 OpenHarmony 社区的适配文档看是否有已知问题清单。查看库的 pubspec.yaml 声明凡是声明了flutter.plugin.platforms且包含android和ios的都需要逐一确认是否加入了ohos平台声明。对于大量依赖原生能力的库如指纹识别、系统分享优先找纯 Dart 实现的替代方案。以本项目的依赖清单为例最终确定可以安全用于 OpenHarmony 的核心依赖只有几个provider状态管理纯 Dart、dio网络请求纯 Dart 实现底层走 socket适配没问题、intl日期/数字格式化纯 Dart、flutter_svgSVG 渲染OpenHarmony 分支支持良好。而某些带原生视图的图表库、视频播放库则全部避开。4.3 运行时性能与内存占用实测OpenHarmony 设备上的 Flutter 性能整体可以满足交易详情这种中低复杂度页面的要求。我分别在一款入门级设备和一宽中端平板上测过冷启动时间都在 1.5 秒以内页面从列表进入详情到数据渲染完成平均耗时 300 毫秒左右。内存方面整个应用在运行时稳定占用 120MB 左右没有出现明显的内存泄漏。有个性能细节值得记录环形进度条的动画帧率在入门设备上会掉到 40 帧左右原因是动画期间整个 SliverList 都会参与重绘。我的优化办法是给进度条组件套上RepaintBoundary让这块独立的绘制区域不跟随列表滚动而重绘。添加之后帧率稳定在 58 帧以上肉眼几乎感觉不到顿挫。5. 常见问题与排查技巧实录实际开发中遇到的问题远比文档里写得多。我这里整理了一些典型问题和对应的排查思路希望能帮你节省排查时间。5.1 图表空白与字体方块问题第一个是图表空白问题。用 CustomPaint 绘制的环形图在某些设备上显示空白重绘时偶尔闪烁。排查下来是自定义的StatefulWidget在didChangeDependencies里没有调用setState触发重绘导致外层主题变化比如日间模式切到夜间模式时Canvas 没有用最新的颜色重绘。解决方案是在didChangeDependencies里检测主题颜色变化并强制重绘override void didChangeDependencies() { super.didChangeDependencies(); final theme Theme.of(context); if (theme.brightness ! _previousBrightness) { _previousBrightness theme.brightness; setState(() {}); } }第二个是字体方块问题我在 3.1 节已经提过根因是系统字体回退机制不同。排查方法是先确认是不是所有文字都显示异常如果只有特定字形异常基本可以断定是字体缺失如果是全部文字异常那就去检查字体文件是否被正确打包进 ohos 的 assets 目录。字体文件的添加方式是在ohos目录下的资源配置文件里声明字体文件路径并在 Flutter 里通过FontLoader加载。5.2 大数据量列表的滚动性能优化交易详情页只有一个交易对象理论上不存在大量列表数据。但“同分类历史交易”区域会展示近三个月的同类交易记录数据量多的时候可能有几百条。如果直接用ListView.builder且没有设置itemExtent每次滚动都需要动态测高性能会明显下降。优化方案有两个第一给ListView.builder设置itemExtent也就是固定每个 item 的高度。交易记录列表的样式非常统一每行高度固定为 64dp设置itemExtent: 64后Flutter 无需动态测量每个 item 的高度滚动性能从 45 帧提升到 60 帧。第二用ListView.separated的separatorBuilder统一绘制分割线避免在每个 item 的底部额外绘制一条线减少不必要的 widget 嵌套。5.3 删除与编辑操作的交互细节删除操作的交互要注意两件事一是删除前必须有二次确认弹窗不能一键直接删掉。偏好设置里如果用户关闭了“删除保护”则直接删除否则弹出确认框。二是删除操作完成后要更新本地缓存的交易列表数据。我当时的做法是删除成功后返回true列表页在返回回调里移除对应记录并弹出 SnackBar 提示。编辑操作的交互同样有讲究。点击编辑按钮进入编辑页编辑完成后的数据要自动同步回详情页。由于详情页的 Provider 持有交易对象的引用编辑页直接修改 Provider 里的状态即可无需重新从网络拉取。这样优化了响应速度用户几乎感觉不到数据刷新过程。6. 页面进一步扩展的一些思路交易详情页做到这个程度已经能覆盖大多数个人理财场景的展示需求。但如果要适配更复杂的场景下面几个方向值得花时间研究这是我个人后续计划的扩展。6.1 交易详情到账单报表的联动跳转详情页底部的“该分类本月消费占比”区域当前点击后还没有跳转逻辑。后续可以增加一个“查看完整报表”入口点击后跳转到该分类下的月度消费明细报表页。报表页除了展示每日消费金额的柱状图还可以加入“平均每日消费”“月度最高单笔消费”等聚合指标让用户对消费习惯有一个全面的画像。6.2 多设备协同场景下的数据同步展示OpenHarmony 的分布式能力很适合做多设备数据协同手机端记录一笔支出平板端或者智慧屏上实时同步显示。实现思路是在数据层引入分布式数据库交易记录通过分布式键值同步到绑定账号的其他设备上。Flutter 层只需要监听数据变化事件调用setState刷新页面即可。难点在于分布式数据库的冲突处理策略——同一笔交易在两个设备上同时被修改以哪个为准通常会用“最后写入时间戳”策略但用户离线场景下的时间戳可信度存疑这也是我目前正在研究的方向。6.3 交易归类智能推荐与 UI 联动详情页还可以加入一个“智能归类”能力用户在输入一条交易标题后系统自动推荐最可能的分类比如“美团”- 餐饮、“滴滴”- 交通。这个推荐引擎可以基于本地规则实现比如关键词匹配与历史分类统计也可以接入云端模型。但需要注意的是在 OpenHarmony 设备上如果要跑本地模型需要同时考虑模型文件体积和推理速度的平衡。实测一个 20MB 的关键词匹配模型冷启动加载要 300ms 左右可接受范围之内但模型超过 50MB 后加载时间会明显增长建议根据设备级别做分级投放。我在实际开发中的体会是交易详情页这种“看似简单”的页面恰恰最考验一个开发者的细节把控能力。数据模型设计是否合理、分组列表在低端机上的滚动是否流畅、删除编辑后的状态同步是否正确每一项都不起眼但合起来决定了用户对这个 App 的第一印象。如果你也在 Flutter 与 OpenHarmony 的交叉领域做应用开发建议先从一个自己最常用的页面练手把适配问题摸熟之后再逐步扩展到更复杂的业务场景。
阅读完成 · 觉得有帮助?
咨询建站