鸿蒙app 健身计划制定(动作视频指导/进度跟踪)
【摘要】 引言个性化健身需求持续增长,用户亟需科学的计划制定、直观的动作指导与精准的进度跟踪。鸿蒙系统的分布式能力、多媒体播放支持及数据可视化特性,为健身计划App提供了高效开发基础,助力用户随时随地获取专业指导并量化训练成果。技术背景鸿蒙框架:基于Stage模型,@Component构建UI,VideoController控制视频播放,Preferences存储计划配置,关系型数据库记录训练日志。视...
引言
技术背景
-
鸿蒙框架:基于Stage模型, @Component构建UI,VideoController控制视频播放,Preferences存储计划配置,关系型数据库记录训练日志。 -
视频指导:通过 Video组件实现本地/在线动作视频播放,支持倍速、全屏与循环播放。 -
进度跟踪:利用图表库(如 @ohos/chart)展示训练完成率、力量增长曲线,结合日历组件标记打卡记录。 -
计划生成:基于用户目标(减脂/增肌)、器械条件与可用时间,通过规则引擎匹配预设模板并动态调整。
应用使用场景
-
新手入门:用户选择“减脂+无器械+每周3次”,App生成含深蹲、开合跳等动作的入门计划,附带分解视频。 -
进阶训练:有经验用户设定“增肌+哑铃+每周4次”,获取分化训练计划(胸/背/腿/肩),跟踪重量进步。 -
居家健身:用户无健身房器械,App推荐徒手动作组合,通过视频纠正姿势,记录每日完成组数。
核心特性
-
智能计划生成:多维度参数(目标、器械、时间)匹配最优训练模板。 -
动作视频指导:高清分解视频+文字要点,支持慢放与循环播放关键帧。 -
三维进度跟踪:日历打卡可视化、数据统计图表化、成就体系激励化。 -
离线可用:计划与视频缓存本地,无网络时仍可训练。
原理流程图与原理解释
流程图
graph TD
A[用户输入需求] --> B[计划生成引擎:匹配模板+动态调整]
B --> C[生成周/日训练计划]
C --> D[展示动作列表:名称+视频+组数/次数]
D --> E[用户训练:播放视频+记录完成量]
E --> F[数据存储:训练日志+进度]
F --> G[可视化:日历打卡+统计图表]
原理解释
-
计划生成:预设多种训练模板(如“新手减脂-无器械”),根据用户选择的“目标强度”(初级/中级)、“可用时间”(30分钟/60分钟)筛选模板,再按周分配具体动作(如周一:热身+深蹲+平板支撑+拉伸)。 -
视频指导:视频文件预置于 resources/rawfile目录,通过Video组件的src属性加载,配合controller控制播放进度与倍速。 -
进度跟踪:训练完成后,将“动作名称、完成组数、实际次数、日期”存入数据库,通过 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.mp4、pushup.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),支持播放/暂停。 -
进度记录:点击“完成训练”后,底部提示“训练记录已保存”,日志存入数据库。
测试步骤
-
环境配置:创建鸿蒙工程,添加权限与代码文件,将动作视频放入 resources/rawfile。 -
模拟器运行:使用API 9+模拟器,运行App,验证计划生成与视频播放正常。 -
功能验证:选择某动作点击“完成训练”,检查日志提示;重启App后确认计划仍显示(需持久化可扩展Preferences存储计划ID)。
部署场景
-
个人手机:作为主力健身工具,离线缓存计划与视频,适合居家/户外训练。 -
智慧屏:投屏至大屏播放视频,动作细节更清晰,适合家庭集体训练。 -
运动手表:通过鸿蒙分布式能力,手表端同步当日计划,轻量级提醒训练。
疑难解答
-
视频无法播放:检查 rawfile目录下视频文件名与代码中enName是否一致(区分大小写),确认video权限已申请。 -
计划生成异常:验证 PlanGenerator中actionLibrary初始化逻辑,确保动作数据非空。 -
日志保存失败:检查数据库初始化是否成功( aboutToAppear中调用init),表结构是否与插入字段匹配。
未来展望与技术趋势与挑战
未来展望
-
AI动作纠正:接入摄像头实时捕捉用户动作,通过姿态估计算法对比标准动作,语音提示错误(如“膝盖内扣”)。 -
动态计划调整:根据训练日志自动增减强度(如连续3次完成目标次数则提升重量/次数)。 -
社交竞技:通过鸿蒙分布式数据共享,好友间PK训练完成率,解锁团队成就。
技术挑战
-
视频加载性能:高清视频占用空间大,需优化缓存策略(如仅缓存本周计划视频)。 -
跨设备同步:不同设备屏幕尺寸差异,视频播放组件需自适应布局。
总结
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)