HarmonyOS运动健康——运动记录与统计概览页面
进度页面是运动健康App的数据汇总页。用户从首页点击"运动记录"或底部导航"进度"跳转到这里,查看本周的运动数据和统计概览。这个页面的任务是:把散落在各处的运动数据(步数、卡路里、时长、次数)汇总成直观的图表和数字,让用户一眼看到"我这周运动了多少"。
页面结构分为两部分:上方的本周活动柱状图和下方的统计概览卡片。柱状图展示每天的趋势变化,统计卡片展示累计总量。从"趋势"到"总量",信息从细节到宏观。
完整效果

柱状图的实现
数据源和渲染
Text('本周活动').fontSize(17).fontWeight(FontWeight.Bold).fontColor(T1)
.width('100%').padding({ left: 16 }).margin({ bottom: 12 })
Row() {
ForEach(WEEKLY_DATA, (day: ActivityRecord) => {
Column() {
Column() {}.width(28).height(day.calories / 15).borderRadius(14)
.linearGradient({ angle: 180, colors: [[A, 0], ['#FF9F43', 1]] })
Text(day.calories.toString()).fontSize(10).fontColor(T2).margin({ top: 6 })
Text(day.date).fontSize(10).fontColor(T3)
}.alignItems(HorizontalAlign.Center).layoutWeight(1)
})
}.width('100%').height(120).padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Bottom).margin({ bottom: 20 })

柱状图用ForEach渲染七天的数据,每天是一个Column(柱子+卡路里数值+日期)。
柱子高度的缩放系数
.width(28).height(day.calories / 15).borderRadius(14)
柱子宽度28px,高度由day.calories / 15计算。和首页的柱状图对比:
| 属性 | 首页 | 进度页 |
|---|---|---|
| 柱子宽度 | 22px | 28px |
| 高度计算 | calories / 20 | calories / 15 |
| 圆角 | 11px(宽度一半) | 14px(宽度一半) |
| 区域高度 | 100px | 120px |
进度页的柱子更宽(28 vs 22)、更高(除以15 vs 除以20)、区域更大(120 vs 100)。这是因为进度页是专门的数据展示页,有更多空间给柱状图。首页的柱状图是"预览",进度页的柱状图是"详情"——同一个图表在不同页面的尺寸不同,符合信息层级。
除以15意味着最大卡路里值不能超过1800(120×15),否则柱子会溢出区域。首页除以20的最大上限是2000(100×20)。两个页面的上限不同,但都没有做动态计算。
底部对齐
.alignItems(VerticalAlign.Bottom)
Row的alignItems(VerticalAlign.Bottom)让所有柱子底部对齐——矮柱子在上面,高的柱子在下面。这是柱状图的标准对齐方式,底部代表"零"。
柱子上方的数值
Text(day.calories.toString()).fontSize(10).fontColor(T2).margin({ top: 6 })
每个柱子上方显示卡路里数值,10px灰色。margin({ top: 6 })在柱子和数值之间留6px间距。数值是固定的——直接显示WEEKLY_DATA中的calories值。
统计概览卡片
2×2 网格布局
Text('统计概览').fontSize(17).fontWeight(FontWeight.Bold).fontColor(T1)
.width('100%').padding({ left: 16 }).margin({ bottom: 12 })
Row({ space: 10 }) {
this.SCard('总步数', '76,532', '本周', A)
this.SCard('总消耗', '3,660', '千卡', '#FF9F43')
}.width('100%').padding({ left: 16, right: 16 }).margin({ bottom: 10 })
Row({ space: 10 }) {
this.SCard('运动时长', '295', '分钟', '#5DADE2')
this.SCard('训练次数', '16', '次', '#00B894')
}.width('100%').padding({ left: 16, right: 16 })

四个统计卡片分成两行,每行两个。用两个Row实现2×2网格,而不是用Grid或Flex-wrap。原因是:ArkUI的Grid组件需要指定列数和行列间距,对于简单的2×2布局来说太重了。两个Row更直观——每个Row放两个卡片,space: 10控制卡片间距。
SCard Builder
@Builder SCard(label: string, value: string, unit: string, color: string) {
Column() {
Text(value).fontSize(28).fontWeight(FontWeight.Bold).fontColor(color)
Text(unit).fontSize(11).fontColor(T3).margin({ top: 4 })
Text(label).fontSize(12).fontColor(T2).margin({ top: 4 })
}.layoutWeight(1).padding(16).backgroundColor('#FFFFFF').borderRadius(16)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.03)', offsetY: 1 })
}
每个统计卡片由三行文字组成:数值(28px加粗彩色)、单位(11px灰色)、标签(12px灰色)。数值最醒目,单位和标签视觉层级较低。
layoutWeight(1)让两个卡片均分Row的宽度。padding(16)给卡片内部留白,避免文字紧贴边框。backgroundColor('#FFFFFF')白色背景+borderRadius(16)圆角+shadow轻微阴影,和首页的训练卡片风格一致。
四个统计指标的含义
| 指标 | 数值 | 单位 | 颜色 | 含义 |
|---|---|---|---|---|
| 总步数 | 76,532 | 本周 | 红色(A) | 一周累计步数 |
| 总消耗 | 3,660 | 千卡 | 橙色(#FF9F43) | 一周累计卡路里消耗 |
| 运动时长 | 295 | 分钟 | 蓝色(#5DADE2) | 一周累计运动时间 |
| 训练次数 | 16 | 次 | 绿色(#00B894) | 一周训练次数 |
四个颜色和首页的数据环一致:红色=步数,橙色=卡路里,蓝色=运动时间,绿色=训练次数。颜色体系在不同页面之间保持统一,用户看到红色就联想到"步数",不管在哪个页面。
数据的硬编码问题
四个统计值(76532、3660、295、16)是硬编码的字符串,不是从WEEKLY_DATA动态计算的。如果把WEEKLY_DATA七天的卡路里加起来,和3660对不上。这意味着柱状图和统计卡片展示的是"假数据"——柱状图来自真实数据源,统计卡片是静态写死的。
实际项目中应该动态计算:
const totalCalories = WEEKLY_DATA.reduce((sum, day) => sum + day.calories, 0)
const totalSteps = WEEKLY_DATA.reduce((sum, day) => sum + day.steps, 0)
但原型阶段硬编码够用——页面布局和交互逻辑已经验证了,数据对接是后续的工作。
页面的数据流
ProgressPage没有@State变量——它不需要响应式渲染。数据来自WEEKLY_DATA(导入的常量)和硬编码的统计值。用户在这个页面只能"看",不能"操作"(没有筛选、排序、编辑功能)。
这种"纯展示"页面的实现最简单——不需要状态管理,不需要事件处理,只需要把数据渲染成UI。但缺点是数据不实时——如果用户刚跑完步,步数不会自动更新。实际项目中应该在onPageShow中刷新数据。
和首页柱状图的复用
首页和进度页都有柱状图,代码几乎一样——都是ForEach渲染WEEKLY_DATA,每天一个Column。区别在于尺寸和样式:首页柱子更窄(22px vs 28px),区域更小(100px vs 120px)。
这两个柱状图没有抽成公共组件——每个页面各自实现了一遍。如果以后要修改柱状图的样式(比如加动画、改颜色),需要改两个地方。更好的做法是抽成一个@Builder WeeklyChart(height: number, barWidth: number),两个页面调用时传入不同参数。
但原型阶段的重复是可以接受的——先验证布局,再优化代码结构。过早抽象反而会增加理解成本。
踩坑记录
柱状图高度溢出。 day.calories / 15的系数15是硬编码的。如果某天卡路里超过1800(120×15),柱子高度会超过区域高度,和其他柱子重叠。首页用20作为系数,上限是2000。两个页面的上限不同,但都没有做动态计算或上限截断。
2×2网格的间距不均。 两个Row各自有space: 10,但Row之间没有设置间距——第二行紧贴第一行。实际上有margin({ bottom: 10 })在第一个Row下方,但第二个Row没有margin。如果卡片高度不一致(比如数值长度不同导致文字换行),两行之间可能出现不对齐。
SCard的layoutWeight。 两个SCard都用layoutWeight(1)均分宽度。如果以后要改成三个卡片一行,需要把layoutWeight和Row的space配合调整。三个卡片时space需要减小,否则间距太大。
统计值的数字格式。 76532显示为"76,532"——有千位分隔符。但这个分隔符是硬编码在字符串里的,不是动态格式化的。实际项目中应该用数字格式化:totalSteps.toLocaleString()。但鸿蒙的toLocaleString支持可能不完整,需要测试。
柱状图的触摸反馈。 柱子没有绑定onClick——用户不能点击某个柱子查看当天的详细数据。如果要加交互,需要在ForEach的Column上绑定onClick,传入day参数,跳转到当天的详情页。
进度页面的核心价值是"数据可视化"——把一周的运动数据变成柱状图和统计卡片。柱状图展示趋势(哪天运动多、哪天运动少),统计卡片展示总量(一共运动了多少)。两个维度互补——趋势告诉用户"哪天偷懒了",总量告诉用户"整体表现如何"。颜色体系和首页保持一致,用户不需要重新学习"红色代表什么"。
更多推荐



所有评论(0)