
鸿蒙健身动作纠正应用开发指南 原创
鸿蒙健身动作纠正应用开发指南
一、系统架构设计
基于HarmonyOS的AI能力和分布式技术,我们设计了一套健身动作纠正系统,主要功能包括:
动作捕捉:通过摄像头实时捕捉用户健身动作
姿态分析:AI分析用户动作与标准动作的差异
实时反馈:即时提供动作纠正建议
训练记录:记录每次训练的动作数据
多设备协同:支持跨设备同步训练计划和进度
!https://example.com/harmony-fitness-coach-arch.png
二、核心代码实现
姿态检测服务
// PoseDetectionService.ets
import camera from ‘@ohos.multimedia.camera’;
import image from ‘@ohos.multimedia.image’;
import pose from ‘@ohos.ai.pose’;
class PoseDetectionService {
private static instance: PoseDetectionService;
private cameraManager: camera.CameraManager;
private poseDetector: pose.PoseDetector;
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private isDetecting: boolean = false;
private constructor() {
this.cameraManager = camera.getCameraManager();
this.poseDetector = pose.createPoseDetector();
public static getInstance(): PoseDetectionService {
if (!PoseDetectionService.instance) {
PoseDetectionService.instance = new PoseDetectionService();
return PoseDetectionService.instance;
public async initCamera(previewSurfaceId: string): Promise<void> {
const cameras = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
throw new Error('No camera available');
this.cameraInput = this.cameraManager.createCameraInput(cameras[0]);
await this.cameraInput.open();
const previewProfile = this.cameraManager.getSupportedOutputCapability(
cameras[0],
camera.ProfileMode.PROFILE_MODE_DEFAULT
).previewProfiles[0];
this.previewOutput = this.cameraManager.createPreviewOutput(
previewProfile,
previewSurfaceId
);
public async detectPose(image: image.Image): Promise<PoseDetectionResult> {
const detectionOptions = {
modelType: pose.ModelType.MODEL_TYPE_HUMAN_POSE_2D,
processMode: pose.ProcessMode.PROCESS_MODE_FAST
};
return new Promise((resolve, reject) => {
this.poseDetector.detect(image, detectionOptions, (err, result) => {
if (err) {
reject(err);
else {
resolve(this.processPoseResult(result));
});
});
private processPoseResult(rawData: any): PoseDetectionResult {
if (!rawData || rawData.poses.length === 0) {
return { poseDetected: false };
const pose = rawData.poses[0];
return {
poseDetected: true,
keypoints: pose.keypoints,
boundingBox: pose.boundingBox,
confidence: pose.confidence,
timestamp: Date.now()
};
public startRealTimeDetection(callback: (result: PoseDetectionResult) => void): void {
this.isDetecting = true;
const interval = setInterval(async () => {
if (!this.isDetecting) {
clearInterval(interval);
return;
try {
const image = await this.captureFrame();
const result = await this.detectPose(image);
callback(result);
catch (error) {
console.error('Detection error:', error);
}, 300); // 每300ms检测一次
public stopRealTimeDetection(): void {
this.isDetecting = false;
private async captureFrame(): Promise<image.Image> {
// 简化实现,实际应从预览流获取帧
return new Promise((resolve) => {
const mockImage = {};
resolve(mockImage as image.Image);
});
}
export const poseDetectionService = PoseDetectionService.getInstance();
动作分析服务
// ExerciseAnalysisService.ets
class ExerciseAnalysisService {
private static instance: ExerciseAnalysisService;
private constructor() {}
public static getInstance(): ExerciseAnalysisService {
if (!ExerciseAnalysisService.instance) {
ExerciseAnalysisService.instance = new ExerciseAnalysisService();
return ExerciseAnalysisService.instance;
public analyzeExercise(
currentPose: PoseDetectionResult,
exerciseType: ExerciseType
): ExerciseAnalysis {
const standardPose = this.getStandardPose(exerciseType);
const comparison = this.comparePoses(currentPose, standardPose);
return {
exerciseType,
accuracy: comparison.accuracy,
deviations: comparison.deviations,
suggestions: this.generateSuggestions(comparison.deviations, exerciseType),
timestamp: Date.now()
};
private getStandardPose(exerciseType: ExerciseType): StandardPose {
// 简化实现,实际应从模型或数据库获取标准姿势
const standardPoses: Record<ExerciseType, StandardPose> = {
'squat': {
keypoints: [
name: ‘left_shoulder’, x: 0.4, y: 0.3 },
name: ‘right_shoulder’, x: 0.6, y: 0.3 },
// 其他关键点...
],
angles: [
name: ‘knee_angle’, min: 90, max: 120 }
},
'pushup': {
keypoints: [
name: ‘left_shoulder’, x: 0.4, y: 0.4 },
name: ‘right_shoulder’, x: 0.6, y: 0.4 },
// 其他关键点...
],
angles: [
name: ‘elbow_angle’, min: 90, max: 120 }
}
// 其他运动类型...
};
return standardPoses[exerciseType] || standardPoses.squat;
private comparePoses(
currentPose: PoseDetectionResult,
standardPose: StandardPose
): PoseComparison {
const deviations: PoseDeviation[] = [];
let matchedPoints = 0;
// 比较关键点位置
standardPose.keypoints.forEach(stdKp => {
const currentKp = currentPose.keypoints.find(kp => kp.name === stdKp.name);
if (currentKp) {
const distance = this.calculateDistance(currentKp, stdKp);
if (distance < 0.1) { // 距离阈值
matchedPoints++;
else {
deviations.push({
pointName: stdKp.name,
deviation: distance,
direction: this.getDirection(currentKp, stdKp)
});
}
});
// 比较关键角度
standardPose.angles.forEach(stdAngle => {
const currentAngle = this.calculateAngle(currentPose, stdAngle.name);
if (currentAngle) {
if (currentAngle < stdAngle.min || currentAngle > stdAngle.max) {
deviations.push({
pointName: stdAngle.name,
deviation: Math.abs(currentAngle - (currentAngle < stdAngle.min ? stdAngle.min : stdAngle.max)),
direction: currentAngle < stdAngle.min ? 'too_small' : 'too_large'
});
}
});
const accuracy = matchedPoints / standardPose.keypoints.length;
return { accuracy, deviations };
private generateSuggestions(deviations: PoseDeviation[], exerciseType: ExerciseType): string[] {
const suggestions: string[] = [];
deviations.forEach(dev => {
const suggestion = this.getSuggestionForDeviation(dev, exerciseType);
if (suggestion) {
suggestions.push(suggestion);
});
if (suggestions.length === 0) {
suggestions.push('动作标准,继续保持!');
return suggestions;
private getSuggestionForDeviation(dev: PoseDeviation, exerciseType: ExerciseType): string | null {
const suggestionMap: Record<string, Record<string, string>> = {
squat: {
knee_angle_too_small: '膝盖弯曲角度不足,尝试蹲得更深一些',
knee_angle_too_large: '膝盖弯曲过度,注意不要超过脚尖',
back_angle_too_small: '背部过于直立,适当前倾保持平衡'
},
pushup: {
elbow_angle_too_small: '肘部弯曲不足,尝试下降得更低',
elbow_angle_too_large: '肘部弯曲过度,注意不要完全伸直',
hip_height_too_high: '臀部位置过高,保持身体成一条直线'
// 其他运动类型的建议…
};
const key = {dev.pointName}_{dev.direction};
return suggestionMap[exerciseType]?.[key] || null;
private calculateDistance(kp1: Keypoint, kp2: Keypoint): number {
return Math.sqrt(Math.pow(kp1.x - kp2.x, 2) + Math.pow(kp1.y - kp2.y, 2));
private getDirection(currentKp: Keypoint, stdKp: Keypoint): string {
const dx = currentKp.x - stdKp.x;
const dy = currentKp.y - stdKp.y;
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? 'too_right' : 'too_left';
else {
return dy > 0 ? 'too_low' : 'too_high';
}
private calculateAngle(pose: PoseDetectionResult, angleName: string): number | null {
// 简化实现,实际应根据关键点计算角度
const angleMap: Record<string, number> = {
knee_angle: 110,
elbow_angle: 100,
back_angle: 170
};
return angleMap[angleName] || null;
}
export const exerciseAnalysisService = ExerciseAnalysisService.getInstance();
训练记录服务
// WorkoutRecordService.ets
import distributedData from ‘@ohos.data.distributedData’;
class WorkoutRecordService {
private static instance: WorkoutRecordService;
private kvManager: distributedData.KVManager;
private kvStore: distributedData.KVStore;
private constructor() {
this.initKVStore();
private async initKVStore(): Promise<void> {
const config = {
bundleName: 'com.example.fitnessCoach',
userInfo: { userId: 'currentUser' }
};
this.kvManager = distributedData.createKVManager(config);
this.kvStore = await this.kvManager.getKVStore('workout_records', {
createIfMissing: true
});
this.kvStore.on('dataChange', (data) => {
this.handleRemoteUpdate(data);
});
public static getInstance(): WorkoutRecordService {
if (!WorkoutRecordService.instance) {
WorkoutRecordService.instance = new WorkoutRecordService();
return WorkoutRecordService.instance;
public async saveWorkoutSession(session: WorkoutSession): Promise<void> {
await this.kvStore.put(session_${session.id}, JSON.stringify(session));
public async getWorkoutSessions(): Promise<WorkoutSession[]> {
const entries = await this.kvStore.getEntries('session_');
return Array.from(entries)
.map(([_, value]) => JSON.parse(value))
.sort((a, b) => b.startTime - a.startTime);
public async getWorkoutSession(id: string): Promise<WorkoutSession | null> {
const value = await this.kvStore.get(session_${id});
return value ? JSON.parse(value) : null;
public async getExerciseHistory(exerciseType: ExerciseType): Promise<ExerciseHistory[]> {
const sessions = await this.getWorkoutSessions();
const history: ExerciseHistory[] = [];
sessions.forEach(session => {
session.exercises.forEach(exercise => {
if (exercise.type === exerciseType) {
history.push({
date: session.startTime,
accuracy: exercise.accuracy,
duration: exercise.duration,
count: exercise.count
});
});
});
return history.sort((a, b) => a.date - b.date);
private handleRemoteUpdate(data: distributedData.ChangeInfo): void {
if (data.deviceId === deviceInfo.deviceId) return;
const key = data.key as string;
if (key.startsWith('session_')) {
const session = JSON.parse(data.value);
EventBus.emit('workoutSessionUpdated', session);
}
export const workoutRecordService = WorkoutRecordService.getInstance();
三、主界面实现
实时训练界面
// WorkoutView.ets
@Component
struct WorkoutView {
@State currentExercise: ExerciseType = ‘squat’;
@State isExercising: boolean = false;
@State analysis: ExerciseAnalysis | null = null;
@State session: WorkoutSession | null = null;
@State previewSurfaceId: string = ‘’;
aboutToAppear() {
this.initCamera();
build() {
Stack() {
// 相机预览
XComponent({
id: 'workoutCameraPreview',
type: 'surface',
libraryname: 'libcamera.so',
controller: this.previewController
})
.onLoad(() => {
this.previewSurfaceId = this.previewController.getXComponentSurfaceId();
poseDetectionService.initCamera(this.previewSurfaceId);
})
.width('100%')
.height('100%')
// 训练信息
Column() {
Text(this.getExerciseName(this.currentExercise))
.fontSize(24)
.fontWeight(FontWeight.Bold)
if (this.session) {
Text(组数: {this.session.currentSet}/{this.session.totalSets})
.fontSize(18)
.margin({ top: 8 })
Text(次数: ${this.session.exercises.find(e => e.type === this.currentExercise)?.count || 0})
.fontSize(18)
.margin({ top: 4 })
}
.alignSelf(ItemAlign.Start)
.margin({ top: 16, left: 16 })
// 分析结果
if (this.analysis) {
ExerciseFeedbackView({ analysis: this.analysis })
.position({ x: '50%', y: '30%' })
// 控制按钮
Button(this.isExercising ? '结束训练' : '开始训练')
.onClick(() => this.toggleWorkout())
.position({ x: '50%', y: '90%' })
}
private async initCamera(): Promise<void> {
await poseDetectionService.initCamera(this.previewSurfaceId);
private toggleWorkout(): void {
if (this.isExercising) {
this.stopWorkout();
else {
this.startWorkout();
}
private async startWorkout(): Promise<void> {
this.session = {
id: generateId(),
startTime: Date.now(),
endTime: 0,
exercises: [],
currentSet: 1,
totalSets: 3
};
this.isExercising = true;
poseDetectionService.startRealTimeDetection((result) => {
if (result.poseDetected) {
this.analysis = exerciseAnalysisService.analyzeExercise(result, this.currentExercise);
// 记录当前动作
this.recordExercise(this.analysis);
});
private async stopWorkout(): Promise<void> {
this.isExercising = false;
poseDetectionService.stopRealTimeDetection();
if (this.session) {
this.session.endTime = Date.now();
await workoutRecordService.saveWorkoutSession(this.session);
}
private recordExercise(analysis: ExerciseAnalysis): void {
if (!this.session) return;
let exercise = this.session.exercises.find(e => e.type === analysis.exerciseType);
if (!exercise) {
exercise = {
type: analysis.exerciseType,
count: 0,
duration: 0,
accuracy: 0,
analyses: []
};
this.session.exercises.push(exercise);
exercise.count++;
exercise.duration += 0.3; // 每次检测间隔0.3秒
exercise.accuracy = (exercise.accuracy * (exercise.count - 1) + analysis.accuracy) / exercise.count;
exercise.analyses.push(analysis);
private getExerciseName(type: ExerciseType): string {
const names: Record<ExerciseType, string> = {
squat: '深蹲',
pushup: '俯卧撑',
lunge: '弓步',
plank: '平板支撑',
'sit-up': '仰卧起坐'
};
return names[type] || type;
}
@Component
struct ExerciseFeedbackView {
private analysis: ExerciseAnalysis;
build() {
Column() {
Text(准确度: ${Math.round(this.analysis.accuracy * 100)}%)
.fontSize(20)
.fontColor(this.getAccuracyColor(this.analysis.accuracy))
if (this.analysis.suggestions.length > 0) {
Column() {
Text('建议改进:')
.fontSize(16)
.margin({ top: 8 })
ForEach(this.analysis.suggestions, (suggestion) => {
Text(• ${suggestion})
.fontSize(14)
.margin({ top: 4 })
})
.margin({ top: 8 })
}
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.shadow({ radius: 4, color: '#00000020' })
private getAccuracyColor(accuracy: number): string {
if (accuracy > 0.9) return '#4CAF50';
if (accuracy > 0.7) return '#FFC107';
return '#F44336';
}
训练历史界面
// HistoryView.ets
@Component
struct HistoryView {
@State sessions: WorkoutSession[] = [];
@State selectedSession: WorkoutSession | null = null;
aboutToAppear() {
this.loadSessions();
EventBus.on(‘workoutSessionUpdated’, () => this.loadSessions());
build() {
Column() {
Text('训练历史')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 16 })
if (this.sessions.length === 0) {
Text('暂无训练记录')
.fontSize(16)
.margin({ top: 32 })
else {
List({ space: 10 }) {
ForEach(this.sessions, (session) => {
ListItem() {
SessionItem({ session })
.onClick(() => this.selectedSession = session)
})
.layoutWeight(1)
}
.padding(16)
private async loadSessions(): Promise<void> {
this.sessions = await workoutRecordService.getWorkoutSessions();
}
@Component
struct SessionItem {
private session: WorkoutSession;
build() {
Row() {
Column() {
Text(formatDate(this.session.startTime))
.fontSize(18)
Text(时长: ${formatDuration(this.session.endTime - this.session.startTime)})
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
.layoutWeight(1)
Column() {
Text(${this.session.exercises.length}个动作)
.fontSize(16)
Text(${this.session.totalSets}组)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
}
.padding(12)
}
function formatDate(timestamp: number): string {
const date = new Date(timestamp);
return {date.getFullYear()}-{date.getMonth() + 1}-{date.getDate()} {date.getHours()}:${date.getMinutes().toString().padStart(2, ‘0’)};
function formatDuration(ms: number): string {
const seconds = Math.floor(ms / 1000);
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return {mins}分{secs.toString().padStart(2, ‘0’)}秒;
动作详情界面
// ExerciseDetailView.ets
@Component
struct ExerciseDetailView {
@State exerciseType: ExerciseType = ‘squat’;
@State history: ExerciseHistory[] = [];
@State standardPose: StandardPose | null = null;
aboutToAppear() {
this.loadHistory();
this.loadStandardPose();
build() {
Column() {
Text(this.getExerciseName(this.exerciseType))
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 16 })
// 标准动作演示
if (this.standardPose) {
Text('标准动作')
.fontSize(18)
.margin({ top: 16 })
StandardPoseView({ pose: this.standardPose })
.height(200)
.margin({ top: 8 })
// 历史数据图表
if (this.history.length > 0) {
Text('进步曲线')
.fontSize(18)
.margin({ top: 24 })
ExerciseProgressChart({ history: this.history })
.height(150)
.margin({ top: 8 })
else {
Text('暂无历史数据')
.fontSize(16)
.margin({ top: 32 })
// 常见错误
Text('常见错误')
.fontSize(18)
.margin({ top: 24 })
CommonMistakesView({ exerciseType: this.exerciseType })
.margin({ top: 8 })
.padding(16)
private async loadHistory(): Promise<void> {
this.history = await workoutRecordService.getExerciseHistory(this.exerciseType);
private async loadStandardPose(): Promise<void> {
this.standardPose = exerciseAnalysisService.getStandardPose(this.exerciseType);
private getExerciseName(type: ExerciseType): string {
const names: Record<ExerciseType, string> = {
squat: '深蹲',
pushup: '俯卧撑',
lunge: '弓步',
plank: '平板支撑',
'sit-up': '仰卧起坐'
};
return names[type] || type;
}
@Component
struct StandardPoseView {
private pose: StandardPose;
build() {
Canvas()
.width(‘100%’)
.height(‘100%’)
.onReady(() => this.drawPose())
private drawPose(): void {
const canvas = this.$canvas;
const ctx = canvas.getContext('2d');
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制关键点
this.pose.keypoints.forEach(kp => {
const x = kp.x * canvas.width;
const y = kp.y * canvas.height;
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#2196F3';
ctx.fill();
// 绘制关键点名称
ctx.font = '12px sans-serif';
ctx.fillStyle = '#000000';
ctx.fillText(kp.name, x + 8, y + 5);
});
// 绘制骨架连线
const connections = this.getPoseConnections();
connections.forEach(conn => {
const kp1 = this.pose.keypoints.find(kp => kp.name === conn[0]);
const kp2 = this.pose.keypoints.find(kp => kp.name === conn[1]);
if (kp1 && kp2) {
ctx.beginPath();
ctx.moveTo(kp1.x canvas.width, kp1.y canvas.height);
ctx.lineTo(kp2.x canvas.width, kp2.y canvas.height);
ctx.strokeStyle = '#2196F3';
ctx.lineWidth = 2;
ctx.stroke();
});
private getPoseConnections(): [string, string][] {
const connections: Record<ExerciseType, [string, string][]> = {
squat: [
['left_shoulder', 'left_elbow'],
['left_elbow', 'left_wrist'],
['right_shoulder', 'right_elbow'],
['right_elbow', 'right_wrist'],
['left_hip', 'left_knee'],
['left_knee', 'left_ankle'],
['right_hip', 'right_knee'],
['right_knee', 'right_ankle']
],
pushup: [
['left_shoulder', 'left_elbow'],
['left_elbow', 'left_wrist'],
['right_shoulder', 'right_elbow'],
['right_elbow', 'right_wrist'],
['left_hip', 'left_knee'],
['left_knee', 'left_ankle'],
['right_hip', 'right_knee'],
['right_knee', 'right_ankle']
// 其他动作的连接…
};
return connections[this.pose.type] || [];
}
四、高级功能实现
多设备训练同步
// WorkoutSyncService.ets
class WorkoutSyncService {
private static instance: WorkoutSyncService;
private constructor() {}
public static getInstance(): WorkoutSyncService {
if (!WorkoutSyncService.instance) {
WorkoutSyncService.instance = new WorkoutSyncService();
return WorkoutSyncService.instance;
public async syncWorkoutPlan(deviceId: string, plan: WorkoutPlan): Promise<void> {
const ability = await featureAbility.startAbility({
bundleName: 'com.example.fitnessCoach',
abilityName: 'WorkoutPlanAbility',
deviceId
});
await ability.call({
method: 'receiveWorkoutPlan',
parameters: [plan]
});
public async syncLiveWorkout(deviceId: string, session: WorkoutSession): Promise<void> {
const ability = await featureAbility.startAbility({
bundleName: 'com.example.fitnessCoach',
abilityName: 'LiveWorkoutAbility',
deviceId
});
await ability.call({
method: 'receiveLiveWorkout',
parameters: [session]
});
public async syncExerciseAnalysis(deviceId: string, analysis: ExerciseAnalysis): Promise<void> {
const ability = await featureAbility.startAbility({
bundleName: 'com.example.fitnessCoach',
abilityName: 'ExerciseAnalysisAbility',
deviceId
});
await ability.call({
method: 'receiveExerciseAnalysis',
parameters: [analysis]
});
public async broadcastToAllDevices(session: WorkoutSession): Promise<void> {
const devices = await deviceManager.getTrustedDevices();
await Promise.all(devices.map(device =>
this.syncLiveWorkout(device.id, session)
));
}
export const workoutSyncService = WorkoutSyncService.getInstance();
训练计划服务
// WorkoutPlanService.ets
class WorkoutPlanService {
private static instance: WorkoutPlanService;
private constructor() {}
public static getInstance(): WorkoutPlanService {
if (!WorkoutPlanService.instance) {
WorkoutPlanService.instance = new WorkoutPlanService();
return WorkoutPlanService.instance;
public generatePlanForGoal(goal: FitnessGoal): WorkoutPlan {
const plans: Record<FitnessGoal, WorkoutPlan> = {
'strength': {
id: generateId(),
name: '力量训练计划',
exercises: [
type: ‘squat’, sets: 4, reps: 8 },
type: ‘pushup’, sets: 3, reps: 10 },
type: ‘lunge’, sets: 3, reps: 12 }
],
frequency: 3,
duration: 4 // 周
},
'endurance': {
id: generateId(),
name: '耐力训练计划',
exercises: [
type: ‘pushup’, sets: 3, reps: 15 },
type: ‘sit-up’, sets: 3, reps: 20 },
type: ‘plank’, sets: 3, duration: 60 } // 秒
],
frequency: 4,
duration: 6 // 周
},
'flexibility': {
id: generateId(),
name: '柔韧性训练计划',
exercises: [
type: ‘stretch’, sets: 2, duration: 30 },
type: ‘yoga’, sets: 3, duration: 45 }
],
frequency: 5,
duration: 8 // 周
};
return plans[goal] || plans.strength;
public adjustPlanBasedOnProgress(plan: WorkoutPlan, progress: ProgressReport): WorkoutPlan {
const adjustedPlan = { ...plan };
// 根据进步情况调整计划
progress.exercises.forEach(exProgress => {
const planExercise = adjustedPlan.exercises.find(e => e.type === exProgress.type);
if (planExercise) {
// 如果完成度超过90%,增加难度
if (exProgress.completionRate > 0.9) {
if (planExercise.reps) {
planExercise.reps = Math.floor(planExercise.reps * 1.1);
else if (planExercise.duration) {
planExercise.duration = Math.floor(planExercise.duration * 1.1);
}
// 如果完成度低于70%,减少难度
else if (exProgress.completionRate < 0.7) {
if (planExercise.reps) {
planExercise.reps = Math.floor(planExercise.reps * 0.9);
else if (planExercise.duration) {
planExercise.duration = Math.floor(planExercise.duration * 0.9);
}
});
return adjustedPlan;
public getDailyPlan(plan: WorkoutPlan, day: number): DailyWorkout {
const exercisesPerDay = Math.ceil(plan.exercises.length / plan.frequency);
const startIndex = (day % plan.frequency) * exercisesPerDay;
const dailyExercises = plan.exercises.slice(startIndex, startIndex + exercisesPerDay);
return {
date: new Date(Date.now() + day 24 60 60 1000),
exercises: dailyExercises,
completed: false
};
}
export const workoutPlanService = WorkoutPlanService.getInstance();
智能提醒服务
// WorkoutReminderService.ets
import reminderAgent from ‘@ohos.reminderAgent’;
class WorkoutReminderService {
private static instance: WorkoutReminderService;
private constructor() {}
public static getInstance(): WorkoutReminderService {
if (!WorkoutReminderService.instance) {
WorkoutReminderService.instance = new WorkoutReminderService();
return WorkoutReminderService.instance;
public async scheduleWorkoutReminders(plan: WorkoutPlan): Promise<void> {
await this.cancelAllReminders();
for (let day = 0; day < plan.duration * 7; day++) {
const dailyPlan = workoutPlanService.getDailyPlan(plan, day);
if (dailyPlan.exercises.length > 0) {
const reminderTime = this.getOptimalWorkoutTime(dailyPlan.date);
const reminderRequest: reminderAgent.ReminderRequest = {
reminderType: reminderAgent.ReminderType.REMINDER_TYPE_TIMER,
actionButton: [{ title: '开始训练' }, { title: '稍后提醒' }],
wantAgent: {
pkgName: 'com.example.fitnessCoach',
abilityName: 'WorkoutReminderAbility'
},
ringDuration: 60,
snoozeTimes: 2,
triggerTime: reminderTime.getTime(),
title: '训练时间到',
content: 今日训练: ${dailyPlan.exercises.map(e => this.getExerciseName(e.type)).join(', ')},
expiredContent: "训练提醒已过期"
};
await reminderAgent.publishReminder(reminderRequest);
}
public async scheduleRestDayReminders(plan: WorkoutPlan): Promise<void> {
for (let day = 0; day < plan.duration * 7; day++) {
const dailyPlan = workoutPlanService.getDailyPlan(plan, day);
if (dailyPlan.exercises.length === 0) {
const reminderTime = this.getOptimalRestDayTime(dailyPlan.date);
const reminderRequest: reminderAgent.ReminderRequest = {
reminderType: reminderAgent.ReminderType.REMINDER_TYPE_TIMER,
actionButton: [{ title: '知道了' }],
wantAgent: {
pkgName: 'com.example.fitnessCoach',
abilityName: 'RestDayReminderAbility'
},
triggerTime: reminderTime.getTime(),
title: '休息日',
content: '今天是休息日,让身体充分恢复',
expiredContent: "休息日提醒已过期"
};
await reminderAgent.publishReminder(reminderRequest);
}
public async cancelAllReminders(): Promise<void> {
const reminders = await reminderAgent.getValidReminders();
await Promise.all(reminders.map(rem =>
reminderAgent.cancelReminder(rem.id)
));
private getOptimalWorkoutTime(date: Date): Date {
// 根据用户偏好返回最佳训练时间
const time = new Date(date);
time.setHours(18, 0, 0, 0); // 默认下午6点
return time;
private getOptimalRestDayTime(date: Date): Date {
const time = new Date(date);
time.setHours(10, 0, 0, 0); // 上午10点
return time;
private getExerciseName(type: ExerciseType): string {
const names: Record<ExerciseType, string> = {
squat: '深蹲',
pushup: '俯卧撑',
lunge: '弓步',
plank: '平板支撑',
'sit-up': '仰卧起坐'
};
return names[type] || type;
}
export const workoutReminderService = WorkoutReminderService.getInstance();
五、总结
本健身动作纠正应用实现了以下核心价值:
精准识别:AI技术准确识别用户健身动作
实时反馈:即时提供动作纠正建议
个性化训练:根据用户目标制定训练计划
多设备协同:支持跨设备同步训练进度
进步追踪:记录训练历史并可视化进步曲线
扩展方向:
增加更多健身动作支持
开发社交分享功能
集成健康数据(心率、卡路里等)
增加AR虚拟教练功能
注意事项:
需要申请ohos.permission.CAMERA权限
动作识别准确率受光线和角度影响
训练建议仅供参考,请根据身体状况调整
多设备协同需保持网络连接
首次使用建议完成身体评估
