news 2026/4/15 17:46:42

小V健身助手开发手记(三):用成就点燃坚持——构建可视化激励系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小V健身助手开发手记(三):用成就点燃坚持——构建可视化激励系统

  • 个人首页: VON

  • 鸿蒙系列专栏: 鸿蒙开发小型案例总结

  • 综合案例 :鸿蒙综合案例开发

  • 鸿蒙6.0:从0开始的开源鸿蒙6.0.0

  • 鸿蒙5.0:鸿蒙5.0零基础入门到项目实战

  • Electron适配开源鸿蒙专栏:Electron for OpenHarmony

  • Flutter 适配开源鸿蒙专栏:Flutter for OpenHarmony

  • 本文所属专栏:鸿蒙综合案例开发

  • 本文atomgit地址:小V健身

小V健身助手开发手记(三)

  • 用成就点燃坚持——构建可视化激励系统
    • 🎯 成就系统的业务目标
    • 🧱 架构设计:数据模型与组件拆分
    • 🖼️ 视觉呈现:从数据到情感
      • 1. 顶部进度环 —— `AchievementTop`
      • 2. 成就徽章网格 —— `AchievementContent`
    • 🔁 数据驱动:如何计算“连续达成天数”?
    • 🎨 设计细节与用户体验
    • 🛠️ 工程亮点总结
    • ✅ 结语
    • 代码总结
      • achieve
      • viewmodel

用成就点燃坚持——构建可视化激励系统

在健康类应用中,用户流失率高是一个普遍难题。研究表明,持续的行为激励是提升用户长期活跃度的关键。为此,我们在「小V健身助手」中引入了「成就系统」,通过可视化徽章、进度反馈与目标达成提示,将枯燥的运动记录转化为一场充满成就感的游戏化旅程。

本篇将详解如何基于 ArkTS 与 HarmonyOS 的声明式 UI 能力,构建一个轻量、可扩展且富有情感温度的成就页面,并探讨其背后的设计逻辑与工程实践。


🎯 成就系统的业务目标

我们的成就模块并非简单堆砌图标,而是围绕三个核心目标设计:

  1. 正向反馈:让用户清晰看到“我做到了什么”;
  2. 行为引导:通过阶梯式目标(如连续3天、7天、30天)鼓励长期坚持;
  3. 情感连接:用温暖的视觉语言传递“你很棒”的肯定。

为此,我们将成就分为两类:

  • 短期成就:如“今日完成3项任务”;
  • 长期成就:如“连续7天达成目标”。

当前版本先聚焦连续达成天数这一最直观的长期指标。


🧱 架构设计:数据模型与组件拆分

整个成就页面由两个核心组件构成:

组件职责
AchievementTop展示当日卡路里消耗进度与目标差距
AchievementContent渲染成就徽章列表

同时引入两个 ViewModel 类,实现数据与视图解耦:

// AchievementInfo.tsexportdefaultclassAchievementInfo{days:number;// 达成所需天数icOn:ResourceStr;// 已解锁图标icOff:ResourceStr;// 未解锁图标}
// AchievementMapInfo.tsexportdefaultclassAchievementMapInfo{off_3=$r('app.media.ic_achieve1_off');on_3=$r('app.media.ic_achieve1_on');// 后续可扩展:off_7, on_7, off_30, on_30...}

💡设计哲学:图标资源通过映射类集中管理,便于未来动态加载或 A/B 测试。


🖼️ 视觉呈现:从数据到情感

1. 顶部进度环 ——AchievementTop

Progress({value:this.value,// 当前消耗(如1500千卡)total:this.task,// 目标值(如3000千卡)type:ProgressType.Ring}).color('#ff2727').backgroundColor('#362423').width('50%').height('95%').style({strokeWidth:25})

  • 使用环形进度条直观展示完成比例;
  • 颜色对比强烈(红 vs 深棕),突出“未完成”的紧迫感;
  • 文字辅助说明:“距离目标还差1500千卡”,强化目标意识。

交互细节:日期区域可点击,调出DateDialog切换查看历史成就——与首页保持一致体验。

2. 成就徽章网格 ——AchievementContent

Flex({direction:FlexDirection.Row,wrap:FlexWrap.Wrap}){ForEach(this.success,(item:AchievementInfo)=>{Column(){Image(this.successDays>=item.days?item.icOn:item.icOff).width('100%').height(88).objectFit(ImageFit.Contain)Text(`已经${item.days}天达成目标`).fontSize(12).fontColor(Color.White)}.width('33%')})}

  • 采用Flex + wrap实现自适应网格布局(3列);
  • 图标根据successDays(当前连续达成天数)动态切换亮/灰状态;
  • 文案统一为“已经X天达成目标”,降低认知负担。

⚠️注意:当前代码中所有AchievementInfo均使用相同的days=3和图标,仅为演示。实际应配置不同天数(3/7/15/30…)。


🔁 数据驱动:如何计算“连续达成天数”?

目前successDays是硬编码的@State successDays: number = 9,但在真实场景中,它应由以下逻辑生成:

// 伪代码:计算连续达成天数functioncalculateStreak(tasks:Task[]):number{letstreak=0;consttoday=newDate();for(leti=0;i<30;i++){// 最多回溯30天constdate=newDate(today);date.setDate(today.getDate()-i);if(isDayCompleted(date,tasks)){streak++;}else{break;// 中断即终止}}returnstreak;}

该函数需结合本地存储的每日任务完成记录进行判断。未来我们将:

  • data_preferences或关系型数据库中持久化每日完成状态;
  • 在每日首次打开 App 时触发calculateStreak
  • 将结果写入AppStorage,供成就页实时读取。

🎨 设计细节与用户体验

元素设计考量
深色背景营造“荣誉殿堂”氛围,突出徽章
白色文字高对比度,确保可读性
图标尺寸统一避免视觉混乱,强调平等价值
文案简洁不解释规则,只陈述事实,降低认知负荷

此外,我们预留了扩展空间:

  • AchievementMapInfo可轻松添加新成就图标;
  • AchievementInfo支持任意天数配置;
  • 未来可加入“点亮动画”、“成就通知”等增强反馈。

🛠️ 工程亮点总结

  1. 声明式 UI + 状态驱动
    所有视觉变化均由@State和条件渲染自动更新,无需手动操作 DOM。

  2. 资源引用标准化
    通过$r('app.media.xxx')统一管理媒体资源,支持多分辨率适配。

  3. 组件复用与职责分离
    AchievementTop可独立用于其他页面(如个人中心),AchievementContent专注成就展示。

  4. 可测试性
    AchievementInfoAchievementMapInfo为纯数据类,便于单元测试。


✅ 结语

成就系统不是锦上添花的装饰,而是用户坚持下去的“精神燃料”。在「小V健身助手」中,我们试图用一行行 ArkTS 代码,传递一句无声的鼓励:“你今天的努力,值得被记住。”

而这,正是技术与人文交汇之处。

代码已通过 HarmonyOS SDK API Version 10+ 验证,适用于 Stage 模型项目。

代码总结

本次主要集中在成就页面的编写

achieve

AchievementContent

importAchievementInfofrom"../../viewmodel/AchievementInfo"importAchievementMapInfofrom"../../viewmodel/AchievementMapInfo"importAchievementTopfrom"./AchievementTop";@Componentexportdefaultstruct AchievementContent{achievementMapInfo:AchievementMapInfo=newAchievementMapInfo()@State successDays:number=9@State success:Array<AchievementInfo>=[newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),newAchievementInfo(3,this.achievementMapInfo.on_3,this.achievementMapInfo.off_3),];build(){Column(){Row(){AchievementTop()}.height('40%')Column(){Row(){Text('成就').fontSize(20).fontWeight(500).fontColor(Color.White)}.width('95%').margin({top:25})Column({space:10}){Flex({direction:FlexDirection.Row,wrap:FlexWrap.Wrap}){ForEach(this.success,(item:AchievementInfo)=>{Column(){Image(this.successDays>=item.days?item.icOn:item.icOff).width('100%').height(88).objectFit(ImageFit.Contain)Text(`已经${item.days}天达成目标`).lineHeight(16).fontSize(12).fontColor(Color.White)}.width('33%').padding({top:38,bottom:10})})}}}.width('100%').height('100%')}.width('100%').height('100%')}}

AchievementTop

importDateDialogfrom"../../dialog/DateDialog"importDateUtilfrom"../../util/DateUtil"@Extend(Text)functiontextStyle(color:ResourceStr,fw:number|FontWeight){.fontSize(20).fontWeight(fw).fontColor(color)}@Componentexportdefaultstruct AchievementTop{@StorageProp('date')date:number=DateUtil.beginTimeOfDay(newDate())controller:CustomDialogController=newCustomDialogController({builder:DateDialog({date:newDate(this.date)})})@State value:number=1500@State task:number=3000build(){Column(){// 前两行Column({space:5}){// 头部日期部分Text(DateUtil.formatDate(this.date)).fontSize(25).fontColor('#b50f0f0f').onClick(()=>{this.controller.open()})Text('健身记录').textStyle('#fff',800)}.width('95%').margin({top:30,bottom:10}).alignItems(HorizontalAlign.Start)Row(){// 左侧文本Column({space:5}){Text('运动消耗').textStyle('#c2c2bf',500)Text(this.value+'/'+this.task+'千卡').textStyle('#85ccb7',800).margin({bottom:20,right:10})Text('距离目标').textStyle('#ccc',500)Text(this.task-this.value+'千卡').textStyle('#a03336',800).margin({right:10})}.alignItems(HorizontalAlign.Start).margin({left:5})// 右侧进度展示Progress({value:this.value,total:this.task,type:ProgressType.Ring}).color('#ff2727').backgroundColor('#362423').width('50%').height('95%').style({strokeWidth:25})}.backgroundColor('#2c2c2a').width('95%').height('190').borderRadius(15)}.width('100%').height('100%')}}

viewmodel

AchievementInfo

exportdefaultclassAchievementInfo{days:number;icOn:ResourceStr;icOff:ResourceStr;constructor(days:number,icOn:ResourceStr,icOff:ResourceStr){this.days=days;this.icOn=icOn;this.icOff=icOff;}}

AchievementMapInfo

exportdefaultclassAchievementMapInfo{off_3:ResourceStr=$r('app.media.ic_achieve1_on');on_3:ResourceStr=$r('app.media.ic_achieve1_on');}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 3:26:08

3步搞定Android移动证书安装:免费快速解决HTTPS抓包难题

3步搞定Android移动证书安装&#xff1a;免费快速解决HTTPS抓包难题 【免费下载链接】MoveCertificate 支持Android7-15移动证书&#xff0c;兼容magiskv20.4/kernelsu/APatch, Support Android7-15, compatible with magiskv20.4/kernelsu/APatch 项目地址: https://gitcode…

作者头像 李华
网站建设 2026/4/15 12:06:03

数据集初识

1.在线加载数据集 代码&#xff1a; import os # 设置环境变量&#xff0c;所有Hugging Face请求都会通过镜像站 os.environ[HF_ENDPOINT] https://hf-mirror.com from datasets import load_dataset,load_from_disk#在线加载数据集 datasets load_dataset(path"lansinu…

作者头像 李华
网站建设 2026/4/5 17:24:01

AFLplusplus模糊测试调试实战:从性能瓶颈到高效优化的完整指南

在模糊测试的世界里&#xff0c;AFLplusplus无疑是众多安全研究者的首选工具。然而&#xff0c;面对复杂的测试场景&#xff0c;如何快速定位问题、优化性能往往成为成功的关键。本文将带你深入掌握AFLplusplus的调试精髓&#xff0c;从基础配置到高级优化&#xff0c;全面提升…

作者头像 李华
网站建设 2026/4/8 22:04:25

Blueprint CSS跨浏览器兼容性完整指南:打造完美网页渲染体验

Blueprint CSS跨浏览器兼容性完整指南&#xff1a;打造完美网页渲染体验 【免费下载链接】blueprint-css A CSS framework that aims to cut down on your CSS development time 项目地址: https://gitcode.com/gh_mirrors/bl/blueprint-css Blueprint CSS框架通过创新的…

作者头像 李华
网站建设 2026/4/8 11:16:08

JUCE音频开发框架:终极跨平台C++音频应用构建指南

JUCE音频开发框架&#xff1a;终极跨平台C音频应用构建指南 【免费下载链接】JUCE 项目地址: https://gitcode.com/gh_mirrors/juce/JUCE JUCE音频开发框架是一个功能强大的跨平台C音频应用开发工具&#xff0c;专为音乐软件开发者、音频工程师和数字信号处理专家设计。…

作者头像 李华