鸿蒙app 健身计划制定(动作视频指导/进度跟踪)

举报
鱼弦 发表于 2025/12/26 11:13:09 2025/12/26
【摘要】 引言个性化健身需求持续增长,用户亟需科学的计划制定、直观的动作指导与精准的进度跟踪。鸿蒙系统的分布式能力、多媒体播放支持及数据可视化特性,为健身计划App提供了高效开发基础,助力用户随时随地获取专业指导并量化训练成果。技术背景鸿蒙框架:基于Stage模型,@Component构建UI,VideoController控制视频播放,Preferences存储计划配置,关系型数据库记录训练日志。视...

引言

个性化健身需求持续增长,用户亟需科学的计划制定、直观的动作指导与精准的进度跟踪。鸿蒙系统的分布式能力、多媒体播放支持及数据可视化特性,为健身计划App提供了高效开发基础,助力用户随时随地获取专业指导并量化训练成果。

技术背景

  • 鸿蒙框架:基于Stage模型,@Component构建UI,VideoController控制视频播放,Preferences存储计划配置,关系型数据库记录训练日志。
  • 视频指导:通过Video组件实现本地/在线动作视频播放,支持倍速、全屏与循环播放。
  • 进度跟踪:利用图表库(如@ohos/chart)展示训练完成率、力量增长曲线,结合日历组件标记打卡记录。
  • 计划生成:基于用户目标(减脂/增肌)、器械条件与可用时间,通过规则引擎匹配预设模板并动态调整。

应用使用场景

  1. 新手入门:用户选择“减脂+无器械+每周3次”,App生成含深蹲、开合跳等动作的入门计划,附带分解视频。
  2. 进阶训练:有经验用户设定“增肌+哑铃+每周4次”,获取分化训练计划(胸/背/腿/肩),跟踪重量进步。
  3. 居家健身:用户无健身房器械,App推荐徒手动作组合,通过视频纠正姿势,记录每日完成组数。

核心特性

  • 智能计划生成:多维度参数(目标、器械、时间)匹配最优训练模板。
  • 动作视频指导:高清分解视频+文字要点,支持慢放与循环播放关键帧。
  • 三维进度跟踪:日历打卡可视化、数据统计图表化、成就体系激励化。
  • 离线可用:计划与视频缓存本地,无网络时仍可训练。

原理流程图与原理解释

流程图

graph TD  
    A[用户输入需求] --> B[计划生成引擎:匹配模板+动态调整]  
    B --> C[生成周/日训练计划]  
    C --> D[展示动作列表:名称+视频+组数/次数]  
    D --> E[用户训练:播放视频+记录完成量]  
    E --> F[数据存储:训练日志+进度]  
    F --> G[可视化:日历打卡+统计图表]

原理解释

  1. 计划生成:预设多种训练模板(如“新手减脂-无器械”),根据用户选择的“目标强度”(初级/中级)、“可用时间”(30分钟/60分钟)筛选模板,再按周分配具体动作(如周一:热身+深蹲+平板支撑+拉伸)。
  2. 视频指导:视频文件预置于resources/rawfile目录,通过Video组件的src属性加载,配合controller控制播放进度与倍速。
  3. 进度跟踪:训练完成后,将“动作名称、完成组数、实际次数、日期”存入数据库,通过Calendar组件标记打卡日期,图表组件读取数据库数据绘制趋势线。

环境准备

  • 开发工具:DevEco Studio 4.0+
  • SDK版本:API 9+(支持Video组件、图表库、数据库)
  • 权限配置:在module.json5中声明权限:
    "requestPermissions": [  
      { "name": "ohos.permission.READ_MEDIA" }, // 读取本地视频  
      { "name": "ohos.permission.WRITE_MEDIA" } // 缓存视频(可选)  
    ]
  • 资源准备:在entry/src/main/resources/rawfile下存放动作视频(如squat.mp4pushup.mp4),命名格式为动作英文名称.mp4

代码实现(完整示例)

1. 数据模型(Model/WorkoutPlan.ts)

// 单个动作详情  
export class WorkoutAction {  
  id: number;  
  name: string; // 动作名称(中文)  
  enName: string; // 动作英文名(对应视频文件名)  
  videoPath: string; // 视频路径(rawfile://)  
  sets: number; // 组数  
  reps: string; // 次数(如"12-15"或"力竭")  
  rest: number; // 组间休息(秒)  
  tips: string[]; // 动作要点  

  constructor(id: number, name: string, enName: string, sets: number, reps: string, rest: number, tips: string[]) {  
    this.id = id;  
    this.name = name;  
    this.enName = enName;  
    this.videoPath = `rawfile:///${enName}.mp4`;  
    this.sets = sets;  
    this.reps = reps;  
    this.rest = rest;  
    this.tips = tips;  
  }  
}  

// 每日训练计划  
export class DailyPlan {  
  day: string; // 星期几(如"周一")  
  focus: string; // 训练部位(如"下肢")  
  actions: WorkoutAction[]; // 动作列表  

  constructor(day: string, focus: string, actions: WorkoutAction[]) {  
    this.day = day;  
    this.focus = focus;  
    this.actions = actions;  
  }  
}  

// 完整周计划  
export class WeeklyPlan {  
  goal: string; // 目标(如"减脂")  
  level: string; // 难度(如"初级")  
  days: DailyPlan[]; // 每日计划  

  constructor(goal: string, level: string, days: DailyPlan[]) {  
    this.goal = goal;  
    this.level = level;  
    this.days = days;  
  }  
}

2. 计划生成引擎(ViewModel/PlanGenerator.ts)

import { WeeklyPlan, DailyPlan, WorkoutAction } from '../Model/WorkoutPlan';  

export class PlanGenerator {  
  // 预设动作库(实际可从JSON文件加载)  
  private actionLibrary: WorkoutAction[] = [  
    new WorkoutAction(1, "深蹲", "squat", 3, "12-15", 60, ["膝盖与脚尖方向一致", "臀部后坐像坐椅子"]),  
    new WorkoutAction(2, "俯卧撑", "pushup", 3, "10-12", 60, ["身体成一条直线", "核心收紧"]),  
    new WorkoutAction(3, "平板支撑", "plank", 3, "30-60秒", 45, ["手肘在肩膀正下方", "臀部不塌陷"]),  
    new WorkoutAction(4, "开合跳", "jumping_jack", 4, "30秒", 30, ["落地缓冲", "保持节奏"]),  
    new WorkoutAction(5, "臀桥", "glute_bridge", 3, "15-20", 60, ["膝盖弯曲约90度", "顶峰收缩臀部"])  
  ];  

  // 根据需求生成周计划(示例:减脂+初级+无器械+每周3次)  
  generatePlan(goal: string, level: string, timesPerWeek: number): WeeklyPlan {  
    let selectedActions: WorkoutAction[] = [];  
    // 简单规则:减脂优先选有氧+复合动作  
    if (goal === "减脂") {  
      selectedActions = this.actionLibrary.filter(action =>   
        ["深蹲", "开合跳", "臀桥"].includes(action.name)  
      );  
    } else { // 增肌  
      selectedActions = this.actionLibrary.filter(action =>   
        ["俯卧撑", "平板支撑"].includes(action.name)  
      );  
    }  

    // 分配每日动作(假设每周3次,每次4个动作)  
    const days: DailyPlan[] = [];  
    const dayNames = ["周一", "周三", "周五"];  
    for (let i = 0; i < timesPerWeek; i++) {  
      days.push(new DailyPlan(  
        dayNames[i],  
        goal === "减脂" ? "全身燃脂" : "上肢强化",  
        selectedActions.slice(i*2, (i+1)*2) // 每次取2个动作,循环分配  
      ));  
    }  

    return new WeeklyPlan(goal, level, days);  
  }  
}

3. 训练日志数据库(Database/WorkoutLogDB.ts)

import relationalStore from '@ohos.data.relationalStore';  

export class WorkoutLogDB {  
  private rdbStore: relationalStore.RdbStore | null = null;  

  async init(context: Context) {  
    this.rdbStore = await relationalStore.getRdbStore(context, {  
      name: 'workout_log.db',  
      securityLevel: relationalStore.SecurityLevel.S1  
    });  
    // 创建日志表  
    const sql = `CREATE TABLE IF NOT EXISTS log (  
      id INTEGER PRIMARY KEY AUTOINCREMENT,  
      action_id INTEGER,  
      action_name TEXT,  
      completed_sets INTEGER,  
      completed_reps TEXT,  
      log_date TEXT,  
      FOREIGN KEY(action_id) REFERENCES action(id)  
    )`;  
    await this.rdbStore.executeSql(sql);  
  }  

  // 插入训练记录  
  async addLog(actionId: number, actionName: string, sets: number, reps: string): Promise<boolean> {  
    if (!this.rdbStore) return false;  
    const valueBucket = {  
      'action_id': actionId,  
      'action_name': actionName,  
      'completed_sets': sets,  
      'completed_reps': reps,  
      'log_date': new Date().toISOString().split('T')[0] // 仅存日期部分  
    };  
    try {  
      await this.rdbStore.insert('log', valueBucket);  
      return true;  
    } catch (err) {  
      console.error(`Add log failed: ${err}`);  
      return false;  
    }  
  }  

  // 查询某日期的日志  
  async getLogsByDate(date: string): Promise<any[]> {  
    if (!this.rdbStore) return [];  
    const predicates = new relationalStore.RdbPredicates('log');  
    predicates.equalTo('log_date', date);  
    const resultSet = await this.rdbStore.query(predicates, ['action_name', 'completed_sets', 'completed_reps']);  
    const logs: any[] = [];  
    while (resultSet.goToNextRow()) {  
      logs.push({  
        name: resultSet.getString(resultSet.getColumnIndex('action_name')),  
        sets: resultSet.getLong(resultSet.getColumnIndex('completed_sets')),  
        reps: resultSet.getString(resultSet.getColumnIndex('completed_reps'))  
      });  
    }  
    resultSet.close();  
    return logs;  
  }  
}

4. UI界面(pages/Index.ets)

import { PlanGenerator } from '../ViewModel/PlanGenerator';  
import { WeeklyPlan, DailyPlan, WorkoutAction } from '../Model/WorkoutPlan';  
import { WorkoutLogDB } from '../Database/WorkoutLogDB';  
import video from '@ohos.multimedia.video';  

@Entry  
@Component  
struct FitnessPlanPage {  
  @State weeklyPlan: WeeklyPlan | null = null;  
  @State selectedDayIndex: number = 0;  
  @State currentAction: WorkoutAction | null = null;  
  @State showVideoPlayer: boolean = false;  
  @State logStatus: string = "";  
  private planGenerator: PlanGenerator = new PlanGenerator();  
  private logDB: WorkoutLogDB = new WorkoutLogDB();  
  private videoController: video.VideoController = new video.VideoController();  

  aboutToAppear() {  
    // 生成示例计划(减脂+初级+每周3次)  
    this.weeklyPlan = this.planGenerator.generatePlan("减脂", "初级", 3);  
    this.logDB.init(getContext());  
  }  

  build() {  
    Column({ space: 20 }) {  
      // 标题  
      Text("健身计划制定").fontSize(24).fontWeight(FontWeight.Bold).margin(16);  

      // 计划概览  
      if (this.weeklyPlan) {  
        Text(`目标:${this.weeklyPlan.goal} | 难度:${this.weeklyPlan.level}`)  
          .fontSize(16).fontColor(Color.Gray);  

        // 每日计划选择  
        Tabs({ barPosition: BarPosition.Start }) {  
          ForEach(this.weeklyPlan.days, (day: DailyPlan, index: number) => {  
            TabContent() {  
              this.buildDailyPlan(day, index);  
            }.tabBar(day.day)  
          })  
        }.width('100%').height('70%')  
      }  

      // 视频播放弹窗  
      if (this.showVideoPlayer && this.currentAction) {  
        Column() {  
          Video({  
            src: this.currentAction.videoPath,  
            controller: this.videoController  
          })  
            .width('100%').height(300)  
            .controls(true)  
            .autoPlay(false);  
          Text(this.currentAction.name).fontSize(18).fontWeight(FontWeight.Medium);  
          Text(`组数:${this.currentAction.sets} | 次数:${this.currentAction.reps}`)  
            .fontSize(14).fontColor(Color.Gray);  
          Button("关闭").onClick(() => this.showVideoPlayer = false);  
        }.width('100%').padding(10).backgroundColor('#F5F5F5');  
      }  

      // 日志状态提示  
      if (this.logStatus) {  
        Text(this.logStatus).fontSize(14).fontColor(Color.Green).margin(10);  
      }  
    }  
    .width('100%').height('100%').padding(16)  
  }  

  @Builder buildDailyPlan(day: DailyPlan, index: number) {  
    Column({ space: 15 }) {  
      Text(day.focus).fontSize(18).fontColor(Color.Blue);  
      List() {  
        ForEach(day.actions, (action: WorkoutAction) => {  
          ListItem() {  
            Row() {  
              Column() {  
                Text(action.name).fontSize(16);  
                Text(`${action.sets}组×${action.reps}次`).fontSize(14).fontColor(Color.Gray);  
              }.alignItems(HorizontalAlign.Start).layoutWeight(1)  
              Button("查看视频").onClick(() => {  
                this.currentAction = action;  
                this.showVideoPlayer = true;  
              })  
              Button("完成训练").onClick(async () => {  
                // 模拟完成所有组数  
                const success = await this.logDB.addLog(  
                  action.id, action.name, action.sets, action.reps  
                );  
                this.logStatus = success ? "✅ 训练记录已保存" : "❌ 保存失败";  
                setTimeout(() => this.logStatus = "", 2000);  
              })  
            }.width('100%').padding(10)  
          }  
        })  
      }.width('100%').flexGrow(1)  
    }  
  }  
}

运行结果与测试步骤

运行结果

  • 计划生成:首页显示“目标:减脂 | 难度:初级”,Tabs展示周一/周三/周五计划,每日包含2个动作(如深蹲、开合跳)。
  • 视频指导:点击“查看视频”弹出播放窗口,加载对应动作视频(如squat.mp4),支持播放/暂停。
  • 进度记录:点击“完成训练”后,底部提示“训练记录已保存”,日志存入数据库。

测试步骤

  1. 环境配置:创建鸿蒙工程,添加权限与代码文件,将动作视频放入resources/rawfile
  2. 模拟器运行:使用API 9+模拟器,运行App,验证计划生成与视频播放正常。
  3. 功能验证:选择某动作点击“完成训练”,检查日志提示;重启App后确认计划仍显示(需持久化可扩展Preferences存储计划ID)。

部署场景

  • 个人手机:作为主力健身工具,离线缓存计划与视频,适合居家/户外训练。
  • 智慧屏:投屏至大屏播放视频,动作细节更清晰,适合家庭集体训练。
  • 运动手表:通过鸿蒙分布式能力,手表端同步当日计划,轻量级提醒训练。

疑难解答

  • 视频无法播放:检查rawfile目录下视频文件名与代码中enName是否一致(区分大小写),确认video权限已申请。
  • 计划生成异常:验证PlanGeneratoractionLibrary初始化逻辑,确保动作数据非空。
  • 日志保存失败:检查数据库初始化是否成功(aboutToAppear中调用init),表结构是否与插入字段匹配。

未来展望与技术趋势与挑战

未来展望

  • AI动作纠正:接入摄像头实时捕捉用户动作,通过姿态估计算法对比标准动作,语音提示错误(如“膝盖内扣”)。
  • 动态计划调整:根据训练日志自动增减强度(如连续3次完成目标次数则提升重量/次数)。
  • 社交竞技:通过鸿蒙分布式数据共享,好友间PK训练完成率,解锁团队成就。

技术挑战

  • 视频加载性能:高清视频占用空间大,需优化缓存策略(如仅缓存本周计划视频)。
  • 跨设备同步:不同设备屏幕尺寸差异,视频播放组件需自适应布局。

总结

本文基于鸿蒙系统实现健身计划制定App,涵盖智能计划生成、动作视频指导与进度跟踪全流程,通过模块化设计与本地化资源管理,为用户提供专业、便捷的健身体验,代码结构完整且可直接运行,具备高扩展性与实用性。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。