资讯动态

Flutter on OpenHarmony体重趋势图实战:从自绘到JSON存储的坑

发布时间:2026/10/23 13:58:35 来源:尧图企业网站定制
在OpenHarmony设备上把体重趋势做成一个流畅的图表页面这件事比我预想中多花了两三天。中途换过一次图表库、重写过一次数据存储最后落地的方案却意外地简单自绘曲线加JSON文件。如果你也在折腾Flutter for OpenHarmony的健康类App或者只是单纯好奇“体重趋势”这种功能从一条条数据变成曲线图中间到底要经历哪些环节这篇实战记录应该能帮你省下不少弯路。我会把环境搭建、数据模型、图表绘制、统计联动和实测踩坑都按顺序拆开讲每一步都交代清楚当时的取舍原因。1. 这个App要做什么以及我为什么选定Flutter on OpenHarmony1.1 需求拆解不只是“记体重”我这个模拟项目X最初的需求很直接让用户每天录入体重App保存历史数据用趋势图展示变化。但一旦开始细化需求就开始膨胀——要能修改和删除记录要按7天、30天、90天查看趋势要显示周期内变化量甚至还想带上BMI计算和分类提醒。健康数据类App有一个天然的特质对真实性的要求比普通业务App高得多。普通列表页展示的是订单、文章或者状态信息偶尔错一个还能容忍体重记录如果小数点后一位显示错了趋势计算、周期对比、BMI换算就全跟着错。这个特质决定了数据模型和存储方案不能随便拍脑袋也决定了后面图表模块必须能精确反映每个数据点。1.2 技术选型一套UI两端跑的关键考量团队里大多数开发者的日常技术栈是Flutter如果只为OpenHarmony单独维护一套原生UI成本至少翻一倍。Flutter在跨端UI一致性和开发效率上的优势是实打实的而OpenHarmony这边有社区维护的Flutter适配方案虽然有一些平台能力限制但用来支撑这种“表单加图表”为主的App完全够用。我做选型时主要看了三件事业务复杂度不涉及复杂硬件能力没有NFC、没有蓝牙外设核心是输入、存储、展示和计算组件依赖图表、日期选择、输入框都可以用Flutter自带能力或自绘完成不依赖稀缺插件团队技能团队成员对Flutter的熟悉度远高于ArkTS原生开发后期维护成本更低。最后还有一个加分项Flutter的渲染机制在图表这类高频刷新场景下有天然优势UI线程和绘制管线分离自绘图表时不容易出现掉帧。1.3 项目架构把数据、计算、绘制三层拆开我没有一开始就写代码而是先把工程分成三层每层定义清楚边界数据层负责体重记录的增删改查、排序、文件读写服务层负责BMI计算、周期统计、变化量计算输出给UI层规范化的数据集合UI层负责列表、表单、图表、设置页这些界面。这个划分在体重趋势功能上体现得特别明显。绘图组件只依赖服务层输出的标准化数据点集不关心这些数据是从文件读的、从数据库读的还是Mock出来的。后续如果要把JSON文件换成SQLite或者引入新的统计维度UI层一行都不用动。后来事实证明这个“先切层再写码”的决定帮我避开了很多坑。比如图表方案从第三方库换成自绘时我只需要改UI层内部实现服务层接口完全没变。2. 工程环境准备让Flutter真正跑在OpenHarmony上2.1 版本匹配是第一道坎在OpenHarmony上跑Flutter并不是官方开箱即用的能力而是依赖社区维护的适配层。第一道坎就是版本匹配Flutter主版本、OpenHarmony SDK版本、适配层的版本必须严格对应。我当时随手拉了一个最新版Flutter结果适配层编译直接报错错误信息指向某个C符号找不到。排查了大半天发现是Flutter版本太新适配层还没来得及同步支持。最后锁定在适配层官方文档声明支持的版本范围内重新创建工程才顺利编译。这里给一个比较稳的操作顺序先查适配层文档确定它支持的Flutter版本范围再安装对应版本的Flutter SDK。OpenHarmony设备端SDK也尽量跟着适配层推荐的版本来不要贪新。2.2 项目初始化和第一跑基础环境准备好之后创建Flutter工程用标准模板即可。和普通Flutter工程相比多出来的步骤是把OpenHarmony的原生工程骨架补充到项目目录里这部分适配层一般会提供脚手架或者文档说明。第一次跑通的过程大概是使用Flutter命令行创建新工程按适配层说明补充OpenHarmony工程目录构建hap包并安装到设备或模拟器通过调试工具连接确认Flutter侧的Log输出正常。第一次启动时如果遇到白屏最可能的原因是原生侧缺少Flutter引擎的初始化调用或者工程里没有正确配置入口模块。这时候优先检查适配层文档中关于工程入口的说明而不是急着改Flutter侧代码。2.3 插件与原生通道的边界策略Flutter on OpenHarmony同样支持MethodChannel但实现机制和Android/iOS略有不同。我的原则是能不走原生通道就不走最大程度减少适配风险。这个项目首版只有一处可能需要走原生通道在后台定时提醒用户记录体重。但考虑到OpenHarmony后台任务本身的限制以及适配层对通知栏的兼容情况我最终把提醒功能延后了。事实证明这个决策是对的首版把全部精力集中在数据记录和趋势展示上所有功能都用纯Flutter能力完成跑起来非常稳定。如果你也计划在OpenHarmony上用Flutter建议把原生通道视为“最后手段”优先寻找纯Flutter解决方案。这样不仅减少调试成本也为将来把代码迁移到Android或iOS留了后路。3. 体重记录的数据模型与存储趋势图的地基3.1 字段设计和单位策略体重记录核心字段只有四个记录ID、体重数值、记录时间戳、备注。ID用于列表key和数据更新时间戳用于排序和按日聚合数值是核心业务数据备注可空用于记录“运动后”“饭后”这类特殊情况。单位方面我的做法是App内部统一使用kg作为唯一存储单位展示层允许切换kg和lb但切换时只做读取转换绝不把lb直接写回存储。比如用户切换到lb模式看到的数据是165.5lb但底层存的仍然是75.1kg。这样避免了一个经典问题存的时候是kg显示的时候是lb用户编辑后写回结果单位混了数据越改越乱。3.2 精度处理用整数对抗浮点误差体重读数通常是0.1kg精度但浮点运算在计算机里有误差。75.6kg在浮点运算后可能变成75.599999展示时如果四舍五入处理不当就会出现“保存前是75.6重新打开变成75.5”的情况。我的解决方案很直接所有核心数值都用整数存储。用户输入75.6kg存的是756用户输入82kg存的是820。所有统计计算都基于整数进行只有到最终展示字符串时才通过统一的格式化方法除以10并补一位小数。这个方案有几个额外好处排序天然准确不会有浮点比较的边界问题JSON文件里的数据可读性高将来如果要加更细的精度比如0.01kg只需要把放大倍数从10改成100迁移成本很低。3.3 JSON文件存储的可行性验证数据存储我最初也考虑过SQLite但最终选了JSON文件。判断依据很简单体重记录的写入频率极低一天最多几条数据量即使积累好几年也就几百KB而JSON文件的好处是可视化、可排查、可手动修改非常适合开发调试。具体实现上我用一个本地文件保存全部记录结构是带有版本号的JSON数组。每次任何写操作完成后把整个数组序列化写回文件。有人会担心“全量写入会不会卡顿”体重这个场景完全不会一条记录才几KB一次序列化加写入在毫秒级。”3.4 服务层接口怎么定义为了让UI层和图表组件用得舒服我把服务层抽成几个明确的查询方法获取全部记录按时间正序获取某时间区间内的记录按日聚合返回每天最后一条记录计算区间变化量、平均值、最小值、最大值计算最新BMI值。这些接口统一输出简单对象或原生集合不暴露任何文件操作细节。UI层拿到数据后直接渲染不用再关心排序和去重。这个设计在后期调试时帮助很大——遇到数据异常只需要在服务层打点日志看入参出参问题一目了然。4. 体重趋势图的绘制自绘曲线全流程4.1 为什么图表库在OpenHarmony上栽了跟头Flutter生态里好看的图表库不少但它们在OpenHarmony上的可用性是个大问题。我在项目里尝试引入一个主流图表库编译能通过运行到趋势图页面时部分手势响应和动画效果直接报“平台通道未实现”的异常。问题根源在于这些图表库的底层能力依赖Flutter对特定平台API的封装而OpenHarmony适配层还没有完整覆盖。考虑到项目时间我最终决定卸载第三方图表库用CustomPainter自绘。自绘的好处是行为完全可控不依赖任何可能缺失的平台通道绘制逻辑是纯Dart代码理论上Android、iOS、OpenHarmony都能跑性能也可以按需优化。缺点是需要自己处理坐标系、刻度、曲线算法但做下来发现工作量完全在可接受范围内。4.2 坐标系与刻度计算自绘图表的第一个核心问题是数据坐标到画布像素坐标的换算。我定义的坐标系是这样的x方向最早日期映射到画布左边缘留出左侧Y轴标签的空间最晚日期映射到画布右边缘y方向数据的最大值和最小值经过上下扩充后映射到画布顶部和底部Y轴底部留出X轴标签空间。代码里最关键的公式是像素x 左内边距 时间比例 × 可用宽度像素y 顶部内边距 数值比例 × 可用高度。有一个容易忽略的细节y轴的最低值不应该直接画到图表底部因为数值范围如果很窄比如74.2到76.8直接映射会让曲线几乎贴在一个水平线上。我会把最小值和最大值各自向外扩展一定比例让曲线有呼吸空间视觉上更自然。4.3 平滑曲线的数学选型直接连接数据点画折线趋势图看起来生硬尤其在体重数据波动的时候像锯齿一样不好看。我最初用二次贝塞尔曲线做平滑取相邻两个数据点的中点作为路径节点原始点作为控制点。这种方案视觉上已经够平滑而且实现简单但有一个问题最终的曲线并不严格经过原始数据点。对于趋势展示来说偏差在像素级用户几乎感知不到但如果将来要支持“点击曲线显示真实数据点”就要确保每个数据点都在曲线上被精确定位。后来我换成了三次Hermite插值单调三次插值核心思路是根据每个点前后相邻点计算该点处的近似切线斜率用Hermite基函数构造每两个点之间的三次曲线段保证相邻段连接处一阶导数连续曲线整体平滑对切线做单调性约束避免出现“过冲”——比如明明相邻两天是下降的趋势曲线却先冲高再回落。换成Hermite插值后曲线严格经过每个真实数据点而且不会出现不合理的抖动。对于体重这种变化平缓的序列效果非常自然。4.4 网格、标签与当前值标注一条干巴巴的曲线可读性很差图表还需要横轴时间刻度按数据跨度自动选择“每天/每周/每月”的刻度间隔纵轴数值刻度根据数据范围生成5到7个刻度并且尽量取整数或半整数避免出现74.7、75.3这种难看刻度背景网格线细线淡化处理辅助用户读值当前值标注在最新数据点上方显示“75.6kg”并画一个实心圆点强调。这些绘制工作都是纯Canvas操作关键是计算好刻度的“漂亮值”。我的做法是先算出数据范围再除以目标刻度数向上取整到最近的步长值然后从步长的整数倍开始生成刻度标签。4.5 点击查看详情与周期切换趋势图加手势交互可以提升不少实用性。我用GestureDetector监听点击然后把点击像素坐标反算回数据坐标找到时间轴上最近的一个数据点弹出该点的日期和体重。这个交互实现的关键是“最近数据点”的定义——我按时间差的绝对值最小来选而不是简单地按x距离。因为图表左右两侧的像素宽度和时间跨度比例在切换周期后可能变化时间差更稳定。周期切换使用分段控件实现7天、30天、90天三档。切换时不是重新创建图表组件而是修改数据源并调用重绘方法。同时时间轴的刻度格式也要跟着变7天视图显示“周一、周二”30天视图显示“3/14、3/16”90天视图则显示“第N周”或月份。5. 统计与记录联动趋势背后的数据游戏5.1 变化量计算的边界条件趋势图顶部的摘要文字需要显示“近30天变化 -1.8kg”这类信息。这个“变化量”的定义是区间最后一条记录值减去区间开始前最近的一条记录值。这里有三个容易踩的边界如果用户今天没有记录区间最后一条记录可能不是今天的数据如果区间开始前没有任何记录起始基线不存在无法计算如果区间内只有一条记录同样无法给出有意义的趋势变化。我的处理方式是只有两端都能取到有效值时才显示数值否则显示“--”并在旁边加一行淡色文字提示“数据不足无法计算趋势”。这种处理比硬给一个错误数值要诚实得多也避免误导用户。5.2 BMI与分类阈值的统一口径BMI计算很简单体重除以身高米数的平方。但要注意口径统一身高取用户设置里的固定值体重取最新一条记录的整数存储值转换回来的浮点值。BMI结果同样保留一位小数否则界面上会出现“体重75.6kgBMI 24.39”这种不一致感。BMI分类阈值我在设置页面让用户可选默认按通用标准低于18.5为过轻18.5到24.9正常25到27.9偏胖28及以上为肥胖。分类判断只使用BMI数值不涉及任何其他身体指标。5.3 增删改之后的刷新机制新增、编辑、删除记录后需要同步刷新三个区域记录列表、趋势图、顶部统计摘要。我采用的方式是ChangeNotifier加手动通知没有引入复杂状态管理库。具体流程是数据层完成写操作服务层重新计算统计UI层收到通知后分别调用列表组件的刷新方法、图表组件的重绘方法、摘要区域的重新绑定方法。手动触发的好处是链路非常清晰出现问题时能快速定位是哪个环节没刷新。数据量级不大的场景根本不需要状态管理框架框架反而会把简单逻辑变得难以追踪。5.4 跨日聚合的时间处理陷阱时间处理是我踩过坑最多的一个环节。最初我直接用DateTime对象保存记录时间然后按日对比结果在跨天边界出现了问题用户在23:59录入的记录到第二天00:01再看“今天”的聚合结果竟然是昨天那条。解决方案是所有记录用时间戳保存聚合时先把时间戳转成当日零点再按这个零点值分组。这样一天内无论几点录入都只会归属到同一天。这个处理很小但不做就会出现偶发的数据错位而且很难被发现。6. 实测中绕不开的坑以及我保留的优化方案6.1 字体与单位显示问题OpenHarmony上的Flutter适配层对某些特殊字符的渲染支持不完整。我第一次在图表Y轴标签里使用了带单位的特殊符号时设备上直接显示成方块。解决方式很粗暴但有效所有界面统一使用纯文本单位“kg”和“lb”不使用特殊符号。同时在小屏设备上做了一次排查确认常用中文文本和数字渲染没有问题。6.2 平台能力缺失时的兼容策略很多在Android上可用的Flutter插件在OpenHarmony上会因为平台通道不存在而运行时报错。为了应对这种情况我把所有平台相关能力集中在单一文件中内部统一捕获异常并返回兜底值。比如日期选择控件我用的是Flutter自带的Material组件完全不需要插件但如果你使用依赖原生能力的三方插件请务必用try-catch包住调用确保在OpenHarmony上失败时不影响主流程。6.3 重绘性能的逐项优化当历史记录超过300条时趋势图刷新开始出现可感知的卡顿。我做了三项针对性优化只在数据变化和周期切换时触发重绘列表滚动、键盘弹起等操作不触发图表重建图表组件外层包裹RepaintBoundary隔离重绘区域避免图表刷新时带动整个页面重绘网格线和静态刻度预先计算并缓存只有尺寸变化时才重新生成绘制时直接复用缓存对象。这三项改完即使在最大跨度视图下拖动和点击也能保持流畅。其实体重数据量很难达到几百条但性能优化这种事早做比晚做好万一用户坚持记录数据量总有一天会涨上来。6.4 体验层面的几个小细节最后说几个单独看不起眼、组合起来却很影响使用感受的细节空数据显示首屏没有记录时图表区域显示“暂无记录”占位提示而不是一个空坐标系输入校验体重值限制在30kg到300kg之间超出范围直接提示避免用户误输入异常值污染趋势间隔提醒如果距离上次记录超过3天提交新记录时温和提醒“建议每天在同一时间测量”但不强制阻断支持最近记录展示列表置顶显示最新一条体重与上次的变化趋势箭头方便用户快速了解近期走向。这些细节不需要复杂技术但每一条都来自实际使用体验。做健康记录App如果只把功能做通而不打磨细节用户流失速度会很快。这次项目做下来我最大的体会是OpenHarmony上的Flutter开发最忌讳拿Android或iOS的成熟经验直接套。图表库、存储插件这些在别的平台看起来顺理成章的选择到这里都可能变成不稳定因素。但只要把数据层、服务层、UI层切开留足替换空间用最朴素的自绘加文件存储方案反而能得到稳定又干净的实现。这个体重趋势模块完成后我把自绘曲线的组件抽出来留给了项目里其他页面复用后面如果要做血压趋势、心率趋势、血糖趋势就只是换数据源和刻度格式的问题了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑