去年年底接到一个项目需求:做一个跑在 OpenHarmony 设备上的猫咪管家 App,功能包括猫咪档案管理、喂养提醒、健康记录,还有我今天要重点讲的这个年龄计算器。整个团队里我主要负责 Flutter for OpenHarmony 侧的落地实现,这个年龄计算器算是整个项目第一个真正提交到设备上验证的功能模块,前前后后踩了不少坑,也理顺了不少设计思路,这篇文章就完整拆解一下这个模块从算法到界面再到平台适配的实战过程。
先交代一下背景。猫咪管家 App 的使用场景是家庭养宠人群,用户需要快速知道自己的猫相当于人类多大年纪。市面上不少宠物App都有年龄换算功能,但做得好的不多,大部分是用一个固定倍数去乘,甚至直接把狗的那套规则套到猫身上,数据偏差很大。我们要做的这个模块需要同时支持“按月龄输入”和“按年龄输入”两种方式,并给出对应的换算结果,再配上性格和行为阶段提示。功能看起来不重,但我把逻辑和交互拆细之后,发现这东西要做得顺手、有说服力,涉及的点比预想多得多。
1. 项目拆解与年龄计算器模块的定位
1.1 核心需求分析
猫咪管家 App 整体定位是轻量级宠物生活工具,目标设备是 OpenHarmony 生态里的平板和带屏幕的智能设备,屏幕尺寸跨度不小,所以第一个要求就是 UI 自适应。年龄计算器作为工具型模块,逻辑上独立,不依赖其他业务数据,非常适合作为团队熟悉 Flutter for OpenHarmony 工具链的切入点。
需求层面拆出这样几个关键点:
- 用户输入猫咪当前年龄,单位可以是“年+月”,也可以只输入月龄。
- 系统输出对应的人类年龄,精确到“岁”。
- 根据年龄段展示猫咪当前所处的生命阶段,比如幼猫、少年、青年、壮年、熟龄、老年。
- 输入和结果区域需要适配不同屏幕宽度,横竖屏都能正常使用。
- 模块支持快捷重置,方便用户反复尝试不同年龄组合。
这里有个容易忽略的细节:用户输入的年龄很多时候是“毛估”的,比如“我家猫大概一岁半”,所以不能只支持整数年,必须把“月”作为一个独立输入维度,否则用户没法精准表达“一岁半”这种常见情况。
1.2 为什么先拿这个模块练手 OpenHarmony 适配
说实话,如果是纯 Flutter 开发,年龄计算器半小时就能写完。但这个项目真正的前提是 Flutter for OpenHarmony 这套工具链我们团队没有人熟,直接上完整 App 风险太高。选年龄计算器作为第一个在 OpenHarmony 真机上运行的模块,有几个理由:
- 它不依赖后端,不需要处理复杂的网络请求,调试链路单纯。
- 它不依赖传感器、摄像头等硬件能力,避免一开始就撞上平台通道缺失的坑。
- 它的交互密度适中,既能验证布局引擎、事件分发、文本渲染这些核心能力,又不会因为逻辑过多导致排查困难。
- 它自带一个相对完整的业务闭环,可以给团队吃一颗定心丸。
把简单模块跑通的意义不在于那点功能,而是验证整套工具链是不是靠谱,后续的复杂功能才有底气叠加。
1.3 技术选型间的取舍
在这个项目之前,团队内部对“到底是用 OpenHarmony 原生 ArkUI 还是 Flutter for OpenHarmony”有过一轮争论。原生方案在平台特性支持上肯定最彻底,但团队里没人写过,整个 App 的代码复用率也是零。Flutter for OpenHarmony 虽然是一个分支版本,但团队对 Flutter 本身熟悉,UI 组件生态、状态管理方案、热重载开发体验都可以直接迁移过来。
另一个好处是 Flutter 的布局能力,特别是对多尺寸屏幕的适配,我们是比较有把握的。OpenHarmony 设备端的屏幕比例五花八门,原生 ArkUI 当然也能做自适应,但 Flutter 这边有更成熟的约束布局体系和现成的测试手段。
最终决定:业务逻辑层全部用 Dart 编写,UI 层用 Flutter Widget,平台相关处理单独隔离到适配层,为后续可能接回标准 Flutter 版本留一条路。
2. 年龄换算逻辑与算法设计
2.1 猫咪年龄换算不是简单乘 7
很多人对“狗龄乘 7、猫龄乘 7”这种说法习以为常,但只要你查过靠谱的兽医资料就会知道,这种线性换算非常粗糙。猫咪的生命周期在前两年变化极快,幼猫出生后第一年就能达到相当于人类 15 岁的性成熟状态,第二年结束可以达到 24 岁左右。之后进入稳定期,每一岁大约相当于人类的 4 岁。如果从第 1 个月开始细算,发育速度是非线性的。
我一开始也想过用一条指数曲线去拟合所有年龄点,比如查到的常见参考曲线是:1 月龄约等于 1 岁人类年龄,2 月龄约等于 3 岁,3 月龄约等于 5 岁,6 月龄约等于 10 岁,9 月龄约等于 13 岁,1 岁约等于 15 岁,2 岁约等于 24 岁,之后每年按 4 岁递增。
如果只用一条公式表达,会非常别扭,而且中间值怎么解释都会被人质疑。所以我换了一个思路:用分段模型。前两年按月龄细粒度处理,两年后按年+月粗粒度递增。这样既符合兽医领域的共识,也便于后续在产品里做天校验和展示。
2.2 分段换算模型的参数推导
分段模型的思路是这样的:
- 小于 1 岁的猫,直接查月龄映射表。
- 满 1 岁不满 2 岁的猫,基础值 15 岁,每多一个月叠加一定岁数。
- 满 2 岁之后,基础值 24 岁,完整年度每增加 1 年加 4 岁。
- 不满一整年的余数月份,按比例折算,满 3 个月加 1 岁。
这里有一个值得讨论的细节:月龄映射表并不是每个月都有权威数据,比如 4 月龄、5 月龄在多数资料里不会单独列出,通常只会描述“3 到 6 个月相当于人类 5 到 10 岁”这样的区间。所以我在 3 到 6 月之间做了线性插值,4 个月取 6 岁,5 个月取 8 岁。同理,6 到 9 月之间取 11 岁和 12 岁作为中间值。
我把整个规则整理成一张计算用表:
| 猫咪月龄/年龄 | 人类参考年龄 | 阶段标签 |
|---|---|---|
| 1 月龄 | 1 岁 | 新生儿期 |
| 2 月龄 | 3 岁 | 婴儿期 |
| 3 月龄 | 5 岁 | 婴儿期 |
| 4 月龄 | 6 岁 | 幼年期 |
| 5 月龄 | 8 岁 | 幼年期 |
| 6 月龄 | 10 岁 | 少年期 |
| 7 月龄 | 11 岁 | 少年期 |
| 8 月龄 | 12 岁 | 少年期 |
| 9 月龄 | 13 岁 | 少年期 |
| 10 月龄 | 14 岁 | 少年期 |
| 11 月龄 | 15 岁 | 青春前期 |
| 1 岁整 | 15 岁 | 青春期 |
| 1 岁 3 个月 | 18 岁 | 青春期 |
| 1 岁 6 个月 | 21 岁 | 青年期 |
| 1 岁 9 个月 | 22 岁 | 青年期 |
| 2 岁整 | 24 岁 | 青年期 |
| 3 岁整 | 28 岁 | 壮年期 |
| 5 岁整 | 36 岁 | 壮年期 |
| 7 岁整 | 44 岁 | 熟龄期 |
| 10 岁整 | 56 岁 | 老年期 |
| 15 岁整 | 76 岁 | 老年期 |
这套表的好处是:每个结论都能查到出处,在使用者提出“为什么我家猫 3 岁才相当于 28 岁”时,我们可以给出合理解释,而不是一句“公式就是这个”。
2.3 代码实现与边界处理
算法写起来并不复杂,我放在一个独立的 Dart 文件里,方便单测。
/// 猫咪年龄换算器 class CatAgeCalculator { // 月龄到人类年龄的基准映射表,支持插值 static const Map<int, int> _monthToHumanMap = { 1: 1, 2: 3, 3: 5, 4: 6, 5: 8, 6: 10, 7: 11, 8: 12, 9: 13, 10: 14, 11: 15, 12: 15, }; /// 根据猫咪年龄计算人类年龄 /// [years] 整年数,[months] 剩余月数 static int calculate({required int years, required int months}) { if (years < 0 || months < 0 || months > 11) { throw ArgumentError('非法输入:years=$years, months=$months'); } final totalMonths = years * 12 + months; if (totalMonths <= 0) { throw ArgumentError('猫咪年龄必须大于0'); } // 两年以内走精确月龄映射 if (totalMonths <= 24) { int base = 15; // 1岁整的基准值 if (totalMonths < 12) { // 直接查月龄表 return _monthToHumanMap[totalMonths] ?? 5; } else if (totalMonths == 12) { return 15; } else { // 1岁到2岁,每满3个月加1岁 int extra = ((totalMonths - 12) / 3).floor(); return base + extra; } } // 两年之后 int result = 24; int extraYears = years - 2; result += extraYears * 4; // 处理剩余月份 if (months >= 6) { result += 2; } else if (months >= 3) { result += 1; } return result; } }这里有几个特别容易出错的点,我逐个说明。
第一个是入参校验。月份等于 12 是用户高频误输入,比如输入“1 年 12 个月”,这种情况必须在 UI 层就拦截掉,换算器内部也要兜底判断,否则会出现“1 年 12 个月算出来比 2 年还小”这种荒谬结果。
第二个是一岁后的月数递增规则。1 岁到 2 岁之间并不是每个月都均匀加岁,因为很多资料建议按季度评估,我最终按“每 3 个月增加 1 岁”处理,比如说 1 岁 3 个月是 16 岁,1 岁 6 个月是 17 岁。有同行提出这样会不会偏小,从实际体验看,反而更容易被用户接受,因为保守估计比高估更可信。
第三个是年限上限。猫能活到 15 岁已经算高龄,但 UI 上用户可能误拖到 30 岁,算法不会崩,但结果会变成一个可笑的数字。我在结果展示时加入了一个判断,超过 20 岁直接显示“高龄区间”,不再展示精确换算值,防止误导。
算法写完之后,我给模块补了一批单元测试。测试用例覆盖了:边界类型(1 月、12 月、24 月、25 月)、非法输入(负数、超范围)、关键节点(6 月、1 岁、1 岁 3 个月、2 岁、2 岁半等)和异常场景。Flutter for OpenHarmony 的分支对 flutter test 的支持我当时还没验证,所以把算法单独抽成纯 Dart 文件后用标准的 Dart 测试跑,这样不依赖 Flutter 引擎也能测,这一步对后面节省了大量时间。
3. Flutter UI 实现与交互细节
3.1 页面布局设计与视觉层级
这个模块虽然是工具性质的,但它是用户打开猫咪管家 App 后会高频触达的功能,所以界面不能太敷衍。我在布局上分了四个区域:
- 顶部标题区:说明用途,放一只猫爪图标。
- 结果展示区:大号数字展示人类年龄,配上一句行为描述。
- 输入操作区:两个选择器,一个管“年”,一个管“月”。
- 底部辅助区:快捷重置按钮和阶段说明卡片。
结果展示区是视觉中心。我用了大号字体加底色圆角卡片,数字部分用系统默认字体加粗,没有用艺术字体,因为 OpenHarmony 分支的字体加载可能有问题,用系统字体最稳妥。卡片的背景色我根据生命阶段做了渐变,幼猫用暖黄色,青年用绿色,老年用蓝灰色,这种细节不需要额外说明用户也会感受到。
布局写法上最需要注意的还是屏幕自适应。我没有用固定宽度的容器,而是用 Column 配合 Expanded 和 Flexible 让内容按比例分布。考虑到设备可能是横屏模式,我把输入区和结果区做成可以在 Row 和 Column 之间切换的布局逻辑。做法很简单:用 OrientationBuilder 包裹,横屏时左右排,竖屏时上下排。
OrientationBuilder( builder: (context, orientation) { if (orientation == Orientation.landscape) { return Row( children: [ Expanded(child: _buildResultCard(context)), SizedBox(width: 24), Expanded(child: _buildInputPanel(context)), ], ); } return Column( children: [ _buildResultCard(context), SizedBox(height: 24), _buildInputPanel(context), ], ); }, )3.2 输入交互方案对比
输入年数这个操作,最开始我考虑的是 TextField 让用户直接敲数字。这个方案实现起来最简单,但实际用下来有几个问题:用户不知道整数范围,容易输负数或者 200 这种离谱值;键盘弹出会遮挡结果区域,横屏时更严重;输入框的焦点管理还需要额外做处理,整体体验很生硬。
后来换成了 Slider 滑动条。滑动条很适合表达“一个连续范围”的选择,用户拖动的过程本身有反馈感,能看到数字跟着变化,比键盘输入灵活多了。但滑动条也有它的短板,就是精确选中小数值比较困难,特别是 0 到 2 岁的幼猫阶段,用户差一个月都会影响结果。
最终我采用了一个折中方案:主交互是滑动条,下面额外提供“年份”和“月份”两个数字标签,点击后可以弹出一个带滚轮的精确选择器。这样既保留了滑动操作的速度感,又给精确输入的刚需留了出口。这个设计算是这个模块里我比较满意的一个点。
年份滑动条的范围是 0 到 20,月份滑动条的范围是 0 到 11。滑动条的颜色根据值域分段显示,比如 0 到 2 岁用浅青色,2 到 10 岁用绿色,10 岁以上用蓝灰色,用户一眼就能判断猫咪当前处于哪个大区间。
3.3 结果展示与状态管理逻辑
结果卡片不仅显示人类年龄,还显示从算法里拿到的阶段标签。我把标签定义成一个枚举:
enum CatLifeStage { kitten('幼猫期', '依赖母乳,需要保暖和频繁喂食'), junior('少年期', '活泼好动,适合培养习惯'), prime('青年期', '精力充沛,注意运动与饮食平衡'), mature('壮年期', '开始需要关注体重与口腔健康'), senior('老年期', '定期体检,注意关节和慢性病管理'); final String label; final String desc; const CatLifeStage(this.label, this.desc); }状态管理这个模块我用的是最简单的方案:一个 StatefulWidget,持有当前年份和月份两个 int 变量,每次发生变化时直接调 CatAgeCalculator.calculate 重新计算结果。不需要引入 Riverpod 或者 Bloc,虽然团队其他业务模块会用,但工具页没有必要为了架构而架构。把依赖缩短,反而让这个页面在热重载时的反馈更快。
不过,直接 setState 会连带重建整个页面,包括没有变化的结果卡片背景渐变动画,导致每次拖动滑动条时界面闪烁。我的优化是:数字文本区域用 ValueListenableBuilder 监听一个 ValueNotifier,只有文本内容变化时才重建那一小块。
final ValueNotifier<int> _humanAge = ValueNotifier(0); void _onAgeChanged({required int years, required int months}) { final age = CatAgeCalculator.calculate(years: years, months: months); _humanAge.value = age; } ValueListenableBuilder<int>( valueListenable: _humanAge, builder: (context, value, child) { return Text('$value 岁', style: ...); }, )3.4 交互反馈与动画细节
动画做得过于花哨在这个模块里没有意义,但我保留了三个克制的反馈:
- 滑动条数值变化时,结果卡片有一个 200 毫秒的缩放动画,模拟“刷新”感。
- 点击重置按钮时,两个滑动条各自做一个从当前值回到默认值的隐式动画,时长 350 毫秒,曲线用 easeOut。
- 生命阶段标签切换时,颜色做一个 300 毫秒渐隐渐现。
缩放的实现用的是 AnimatedContainer,没有用 AnimationController,因为前者更轻量,代码可读性也更好。价值在于让用户感知到输入变化和结果刷新之间的因果关系,如果结果数字是瞬间跳变的,用户容易怀疑是不是自己没拖到位。
动画这块需要注意的是 OpenHarmony 分支对动画性能的支撑。我在真机上测试 60fps 的刷新率,简单隐式动画没有问题,但如果同时开两个 AnimatedContainer 再加上背景渐变,就会明显掉帧。所以我在这个页面里只保留一个动画源,其他元素全部用静态颜色切换。
4. OpenHarmony 平台适配实战细节
4.1 编译工具链的差异与配置
Flutter for OpenHarmony 不是标准 Flutter 官方直接支持的平台,需要配合 OpenHarmony 的 SDK 和配套 IDE 来构建。整个开发环境的链路比纯 Flutter 要长一些,我在配置阶段反复折腾了几次,最后归纳起来核心就是三步:
- 安装 OpenHarmony SDK,并配置好 HarmonyOS 相关的环境变量。
- 在 Flutter 项目里启用对应分支的 Flutter SDK。
- 在 IDE 中配置项目签名,生成 hap 安装包,再用调试工具推送到真机。
编译命令上,标准 Flutter 打 apk 是 flutter build apk,而 OpenHarmony 分支是 flutter build hap。这个命令直接在 README 里就能看到,但容易忽略的是构建前必须先把签名信息配置好,否则构建出来的 hap 装不进设备。签名这个问题卡了我差不多一个下午,一开始一直报权限错误,后来才发现是要先在 IDE 里生成密钥库文件,再把它路径配置到构建环境里。
提示:Flutter for OpenHarmony 的项目结构虽然和标准 Flutter 项目一致,但 build 目录下的产物格式、输出路径都不一样,不要用 apk 的思路去找 hap 文件。
4.2 真机调试与日志排查经验
OpenHarmony 真机调试使用 hdc 命令,体系上和 adb 类似,但具体命令名不同。我在调试这个模块时的高频操作是:
# 查看连接的设备 hdc list targets # 安装hap包 hdc install 目标路径/xxx.hap # 拉取设备日志 hdc shell hilog一开始我用 hilog 拉全套日志,信息量太大根本看不过来。后来发现 Flutter 框架层的输出会集中在一个标签下,直接过滤这个标签,配合打印日志定位问题效率会高很多。这一步说白了就是一个日志过滤的问题,但对不熟悉 OpenHarmony 的 Flutter 开发者来说,等于要先学一套新的日志体系,成本还是有的。
我推荐的做法是:在代码里通过一个自定义的 debugPrint 包装函数统一打印,并且在打印前加上模块前缀,比如 “[CatAge]”。然后把所有的关键路径,比如计算开始、输入变化、结果更新阶段,都打上这个前缀。这样在 hilog 里过滤关键字,可以快速定位是逻辑问题还是 UI 问题。
4.3 触摸事件与焦点管理的差异
页面里的 Slider 在标准 Flutter 上拖拽很顺滑,但在 OpenHarmony 分支上,我第一次真机拖的时候就发现一个现象:手指按下后滑动,滑块会滞后又回弹,感觉 UI 线程被卡住了。排查后确认不是渲染性能问题,而是事件通道的采样频率和 Flutter 框架预期不一致。
解决办法比较取巧。我没有改造事件分发,而是把滑块的通道路径切换成这不依赖原生滚动惯性,完全由 Flutter 侧的 GestureDetector 识别手势,再自己更新滑块位置。说白了,是规避了分支里滑动事件的已知问题。第二个方案是把滑块换成自定义实现:
GestureDetector( onHorizontalDragUpdate: (details) { final localDx = details.localPosition.dx; final width = constraints.maxWidth; final ratio = (localDx / width).clamp(0.0, 1.0); setState(() { _years = (ratio * 20).round(); }); }, child: ..., )这个方案的好处是完全不依赖原生 Slider 的手势实现,事件分发链路大概率稳定。后来我把整个输入区的年月滑块全部换成这套自定义手势,真机上验证下来,拖拽响应和标准 Flutter 几乎没有差别。这里的经验是:碰到平台差异时,与其等框架修复,不如绕开可能出问题的控件,用最基础的手势组件替代,毕竟工具模块的控件复杂度低,完全可控。
4.4 插件生态的空白与最小化依赖策略
Flutter 的优势之一是庞大插件生态,但在 Flutter for OpenHarmony 分支上,大量第三方插件只有 Android/iOS 原生实现,没有 OpenHarmony 的实现。我在这个模块里尽量做到零插件依赖。比如结果分享功能,按理说可以用 share_plus 插件,但我在 OpenHarmony 上跑测试直接 MissingPluginException,后来就改为调用系统分享组件或者干脆先砍掉,等后续通过平台通道实现。
我的核心策略是:凡是能自己写的逻辑,绝不依赖插件;凡是必须使用平台能力的,先隔离到一个抽象接口,等原生适配完成再填充实现。这样保证模块在 OpenHarmony 上不会因为一个插件的缺失而整个崩溃。
我把模块里所有平台相关的能力抽象成两个接口,一个是提示框展示,一个是日志上传,各自有默认的 Flutter 实现和 OpenHarmony 实现。默认实现归默认,到真机再根据运行环境切换。
5. 测试、问题排查与优化记录
5.1 功能性测试用例全覆盖
年龄计算器是一个纯计算加展示的模块,测试起来非常爽,因为它没有外部依赖。我拿到算法的第一版后,给测试同事出具了一份用例表,覆盖这么几类:
| 用例场景 | 输入 | 预期输出 | 验证重点 |
|---|---|---|---|
| 新生幼猫 | 0年1月 | 1岁 | 最小边界 |
| 满周岁 | 1年0月 | 15岁 | 周岁节点 |
| 成年初期 | 2年0月 | 24岁 | 第二阶段起点 |
| 成熟期 | 5年3月 | 32岁 | 年份+余月折算 |
| 老年期 | 12年6月 | 62岁 | 上限区间 |
| 非法输入 | 0年12月 | 提示错误 | UI层拦截 |
这几个用例在开发阶段就全部通过了,但在真机上还是出现过不一致的情况,原因出在 UI 层的月份上限控制,因为用户在界面上可以通过月份滑块滑到 11,但年份滑动到 1 之后,理论上应该隐藏或者锁定月份滑块,否则会出现 1 年 11 月这种还算合法但接近 2 年的值,和“2 年 0 月”差别不大。后来我加了一个联动逻辑:当年份等于 0 时月份可选 1 到 12,年份大于等于 1 时月份可选 0 到 11,避免用户写出“1 年 12 月”这种输入。
这个小小的交互约束,实际上是实际测试中发现并修复的,也是这类工具类页面的一个通病。
5.2 渲染性能与功耗优化
模块在低端设备上的性能问题,我从两个方面做了优化。第一个是大面积背景用的渐变色,OpenHarmony 分支在渲染复杂渐变时开销比较高,我退了一步,把多层渐变换成纯色卡片加少量阴影。视觉上变化不大,但帧率从之前偶发的 30 fps 稳回到 58 fps 左右。第二个是结果数字的刷新,当滑动条连续触发计算时,如果每次都重建整个子组件树,OpenHarmony 分支的编译器还做不到标准的帧调度优化。我用 ValueListenableBuilder 圈定了重建范围,这个在前面已经提到过。
从监控数据上看,这个页面的平均渲染耗时从初版的 18ms 降低到 9ms 左右,整体性能表现已经和同屏原生页面接近。对一个工具页来说,这个数字完全够用。
另外我还顺手做了一项省电优化:滑动条拖动过程中禁止结果卡片里的辅助说明文本重排,只更新数字区域。手机上看不出功耗差别,但在平板上长时间挂机展示时,能明显降低发热。
5.3 真机回归测试记录
这个模块经过了三轮回归测试,我把其中最有代表性的一次真机测试记录整理出来。
第一轮是在标准手机尺寸的竖屏设备上测的。主要验证交互流程:从默认值拖动年份滑条到 2,再拖动月份滑条到 6,结果是 26 岁,逻辑正确,页面无卡顿。然后点击重置,滑块动画回位,结果恢复为默认。
第二轮是在横屏平板上测的。因为布局变成了左右分栏,我需要确认左右栏在宽度比较大时不会拉伸过度。结果发现问题:结果卡片里的数字字号是固定值,在宽屏下显得偏小,比例失衡。后来我把字号改成用 LayoutBuilder 的约束宽度动态计算,宽度越宽,字号越大,但设了一个上限值。
第三轮是在低端配置的设备上跑的。主要问题是滑动条拖动时偶发掉帧,定位到是隐式动画和 ValueListenableBuilder 叠加导致的同步压力,我把动画时长从 250ms 改到 350ms,并给动画加了一个挂起条件,当滑动条连续变化时不触发缩放动画,只在松手后触发一次。这个调整非常有效,掉帧率基本降为零。
我可以给同样在做这类适配的同学一个建议:回归测试时一定要覆盖低端设备,不要只在主力机上测,不然一些渲染性能类的坑会在上线后集中爆发。
6. 关于拓展与延续
这个年龄计算器只是猫咪管家 App 里的一个起点,但它在整个项目里的意义不只是一张换算表,更是我们团队在 Flutter for OpenHarmony 上跑通第一条完整链路的关键验证。算法代码很轻,界面上也不复杂,真正有价值的是它暴露出来的平台适配问题,以及为了解决这些问题沉淀下来的经验。
后面我的计划是把这套换算能力复用到 App 的其他位置,比如猫咪档案详情页接入换算结果展示,健康记录模块根据猫咪年龄给出疫苗接种建议,甚至可以配合提醒功能,在猫咪进入老年期时推送体检提醒。换算算法本身可以抽成一个独立包,核心计算不依赖 UI,这样无论未来是继续在 OpenHarmony 上做,还是把代码迁回标准 Flutter 环境做多端发布,都不需要改动业务逻辑。
如果你也在做类似的宠物类工具 App,我的建议是先别急着堆功能,把这种看似简单的小模块认认真真做一遍,从算法口径到边界处理,从 UI 适配到平台兼容,全程走通一次,后面整个项目的底气就完全不一样。