HarmonyOS5 运动健康app(三):健康睡眠(附代码)

259 阅读6分钟

智能睡眠监测应用

一、睡眠监测系统的数据建模与状态定义

该应用通过四层数据结构构建睡眠监测的数字基础:

  1. 睡眠阶段枚举( SleepStageType
    定义清醒(AWAKE)、浅度(LIGHT)、深度(DEEP)三种核心状态,为睡眠周期分析建立分类标准:
enum SleepStageType {
  AWAKE = 'awake',
  LIGHT = 'light',
  DEEP = 'deep'
}

2. 阶段配置接口( SleepSTyeS
存储各阶段的可视化属性与时间参数,如图标路径、持续时间范围及转换概率:

interface SleepSTyeS {
  icon: string;          // 阶段图标
  title: string;         // 阶段名称
  color: string;         // 对应颜色
  minDuration: number;   // 最小持续时间(秒)
  maxDuration: number;   // 最大持续时间(秒)
  transitionChance: number; // 切换概率
}

3. 阶段数据接口( SleepStage
记录单次阶段的完整信息,包括开始/结束时间、时长及占比数据:

interface SleepStage {
  type: SleepStageType;  // 阶段类型
  icon: string;          // 图标路径
  title: string;         // 阶段标题
  duration: string;      // 持续时长
  color: string;         // 阶段颜色
  startTime: Date;       // 开始时间
  endTime: Date;         // 结束时间
  percentage: string;    // 时长占比
}

4. 睡眠记录接口( SleepRecord
聚合完整睡眠周期数据,形成可追溯的历史记录:

interface SleepRecord {
  id: string;            // 记录ID
  startTime: Date;       // 开始时间
  endTime: Date;         // 结束时间
  duration: string;      // 总时长
  stages: SleepStage[];  // 阶段列表
  qualityScore: number;  // 质量评分(0-100)
}

这种分层建模方式使数据既能满足实时展示需求(如当前阶段),又能支持深度分析(如历史周期趋势),为算法评估提供结构化支撑。

二、睡眠周期的动态模拟与状态机实现

应用通过状态机模式驱动睡眠阶段的自动切换,核心逻辑集中在三个关键方法:

  1. 计时器驱动的状态更新
    通过setInterval实现1秒精度的时间追踪,同时触发阶段检查:
startSleep() {
  this.timerId = setInterval(() => {
    this.sleepDuration = this.calculateDuration(this.startTime, new Date());
    this.currentStageDuration++;
    this.checkStageTransition();  // 检查阶段切换
  }, 1000);
}

2. 阶段切换策略算法
结合时间阈值与随机概率实现自然的周期波动:

checkStageTransition() {
  const config = this.stageConfig[this.currentStage];
  if (this.currentStageDuration >= config.minDuration) {
    if (this.currentStageDuration >= config.maxDuration) {
      this.transitionToNextStage();  // 强制切换
    } else if (Math.random() < config.transitionChance) {
      this.transitionToNextStage();  // 概率切换
    }
  }
}
    • 达到最小持续时间后,按配置概率触发切换(如浅度睡眠以40%概率进入深度睡眠)
    • 超过最大持续时间则强制切换,模拟真实睡眠周期规律
  1. 状态流转控制
    通过定义阶段转换规则,模拟睡眠周期的自然波动:
transitionToNextStage() {
  const now = new Date();
  this.saveCurrentStage(now);  // 保存当前阶段
  
  // 按规则计算下一阶段(如清醒阶段80%概率进入浅度睡眠)
  switch (this.currentStage) {
    case SleepStageType.AWAKE:
      this.currentStage = Math.random() < 0.8 ? SleepStageType.LIGHT : SleepStageType.DEEP;
      break;
    // 其余阶段转换逻辑...
  }
}

这种动态模拟机制无需真实传感器数据,即可通过算法还原睡眠周期的自然波动,为用户提供接近真实场景的阶段分析。

三、睡眠质量评估的多维度算法设计

应用通过三层指标体系构建睡眠质量评分(0-100分),算法实现兼顾专业性与可解释性:

  1. 深度睡眠占比分析
    以深度睡眠占比20%-30%为基准,每超出1%加1分,不足1%减1分:
const deepStage = this.sleepStages.find(s => s.type === SleepStageType.DEEP);
if (deepStage) {
  const deepPercentage = (deepSeconds / totalSeconds) * 100;
  score += deepPercentage > 30 
    ? Math.min(20, Math.round(deepPercentage - 30)) 
    : deepPercentage < 20 
      ? -Math.min(20, Math.round(20 - deepPercentage)) 
      : 0;
}

2. 清醒状态惩罚机制
清醒占比超过10%后,每超出1%减2分,量化碎片化睡眠的影响:

const awakeStage = this.sleepStages.find(s => s.type === SleepStageType.AWAKE);
if (awakeStage) {
  const awakePercentage = (awakeSeconds / totalSeconds) * 100;
  score -= awakePercentage > 10 
    ? Math.min(30, Math.round((awakePercentage - 10) * 2)) 
    : 0;
}

3. 周期完整性评估
每完成3个阶段(约一个完整睡眠周期)加2分,最多加10分:

const cycleCount = Math.floor(this.sleepStages.length / 3);
score += Math.min(10, cycleCount * 2);

最终评分会转换为自然语言描述(如"良好:睡眠质量不错"),降低非专业用户的理解门槛。

四、交互界面的分层设计与数据可视化

应用采用"状态卡片+阶段分析"的双层UI架构,通过ArkTS声明式UI实现动态渲染:

  1. 睡眠状态主卡片
Stack() {
  Circle().fill(this.isSleeping ? '#3498db' : '#4682b4')
    .width(200).height(200).opacity(0.9);
  Column({ space: 8 }) {
    Text(this.sleepDuration).fontSize(32).fontWeight(FontWeight.Bold);
    if (this.isSleeping) {
      Text(`当前: ${this.stageConfig[this.currentStage].title}`)
        .backgroundColor(this.stageConfig[this.currentStage].color + '50')
        .padding(5).borderRadius(5);
    }
  }
}
    • 圆形进度条背景(蓝色表示睡眠中,深蓝色表示已结束)
    • 实时显示睡眠时长、阶段状态及时间信息
    • 状态切换按钮通过颜色区分操作(绿色"开始"、红色"起床")
  1. 阶段分析面板
ForEach(this.sleepStages, (stage: SleepStage) => {
  Column({ space: 8 }) {
    Image(stage.icon).width(60).height(60)
      .backgroundColor(stage.color + '10').borderRadius(10);
    Text(stage.duration).fontSize(14).fontColor('#666666');
    Text(stage.percentage).fontSize(12)
      .backgroundColor(stage.color + '10').padding(5);
  }
});
    • 横向排列各阶段卡片,包含图标、时长及占比数据
    • 评分卡片采用"数字+描述"双行显示(如"75/100 良好:睡眠质量不错")
    • 空状态提示优化无数据场景的用户体验

这种设计遵循"数据可视化金字塔"原则——顶层展示核心指标(时长/评分),中层呈现阶段分布,底层隐藏时间戳等辅助数据,使信息层级清晰可辨。

五、附:代码

import promptAction from '@ohos.promptAction';

// 睡眠阶段类型
enum SleepStageType {
  AWAKE = 'awake',
  LIGHT = 'light',
  DEEP = 'deep'
}

// 睡眠阶段数据接口
interface SleepStage {
  type: SleepStageType;  // 睡眠阶段类型
  icon: string;          // 图标资源路径
  title: string;         // 睡眠阶段标题
  duration: string;      // 该阶段持续时长
  color: string;         // 阶段对应颜色
  startTime: Date;       // 阶段开始时间
  endTime: Date;         // 阶段结束时间
  percentage: string;    // 占总睡眠时长百分比
}

// 睡眠记录接口
interface SleepRecord {
  id: string;
  startTime: Date;
  endTime: Date;
  duration: string;
  stages: SleepStage[];
  qualityScore: number;  // 睡眠质量评分 (0-100)
}

interface SleepSTye {
  SleepStageType:SleepStageType
  SleepSTyeS:SleepSTyeS
}


interface SleepSTyeS {
  icon: string;
  title: string;
  color: string;
  minDuration: number;
  maxDuration: number;
  transitionChance: number;
}

@Entry
@Component
export struct Index {
  @State sleepDuration: string = "0时0分0秒";    // 总睡眠时长
  @State startTime: Date = new Date();            // 开始时间
  @State endTimeStr: string = "0:0:0";            // 结束时间字符串
  @State isSleeping: boolean = false;             // 是否正在睡眠
  @State timerId: number = -1;                    // 计时器ID
  @State currentStage: SleepStageType = SleepStageType.LIGHT; // 当前睡眠阶段
  @State currentStageStartTime: Date = new Date(); // 当前阶段开始时间
  @State currentStageDuration: number = 0;        // 当前阶段持续时间(秒)
  @State sleepStages: SleepStage[] = [];          // 所有睡眠阶段记录
  @State sleepRecords: SleepRecord[] = [];        // 历史睡眠记录

  // 睡眠阶段配置
  private stageConfig: Record<SleepStageType, SleepSTyeS> = {
    [SleepStageType.AWAKE]: {
      icon: "$r('app.media.awake_sleep_icon')",
      title: "清醒睡眠",
      color: "#e74c3c",
      minDuration: 30,
      maxDuration: 180,
      transitionChance: 0.7
    },
    [SleepStageType.LIGHT]: {
      icon: "$r('app.media.light_sleep_icon')",
      title: "浅度睡眠",
      color: "#2ecc71",
      minDuration: 120,
      maxDuration: 600,
      transitionChance: 0.4
    },
    [SleepStageType.DEEP]: {
      icon: "$r('app.media.deep_sleep_icon')",
      title: "深度睡眠",
      color: "#3498db",
      minDuration: 300,
      maxDuration: 1200,
      transitionChance: 0.2
    }
  }

  // 开始睡觉
  startSleep() {
    this.isSleeping = true;
    this.startTime = new Date();
    this.endTimeStr = "睡眠中...";
    this.sleepDuration = "0时0分0秒";
    this.currentStage = SleepStageType.LIGHT; // 初始为浅度睡眠
    this.currentStageStartTime = new Date();
    this.currentStageDuration = 0;
    this.sleepStages = [];

    // 启动计时器
    this.timerId = setInterval(() => {
      const now = new Date();
      this.sleepDuration = this.calculateDuration(this.startTime, now);
      this.currentStageDuration++;

      // 检查是否需要切换睡眠阶段
      this.checkStageTransition();
    }, 1000);

    promptAction.showToast({ message: "开始记录睡眠" });
  }

  // 结束睡觉
  endSleep() {
    clearInterval(this.timerId);
    this.isSleeping = false;
    const endTime = new Date();
    this.endTimeStr = this.formatTime(endTime);
    this.sleepDuration = this.calculateDuration(this.startTime, endTime);

    // 保存最后一个睡眠阶段
    this.saveCurrentStage(endTime);

    // 计算各阶段百分比
    this.calculateStagePercentages();

    // 计算睡眠质量评分
    const qualityScore = this.calculateQualityScore();

    // 保存睡眠记录
    const newRecord: SleepRecord = {
      id: Date.now().toString(),
      startTime: this.startTime,
      endTime: endTime,
      duration: this.sleepDuration,
      stages: this.sleepStages,
      qualityScore: qualityScore
    };

    this.sleepRecords = [newRecord, ...this.sleepRecords];

    promptAction.showToast({
      message: `睡眠记录已保存,质量评分: ${qualityScore}`
    });
  }

  // 检查是否需要切换睡眠阶段
  checkStageTransition() {
    const config = this.stageConfig[this.currentStage];

    // 如果达到最小持续时间,随机决定是否切换
    if (this.currentStageDuration >= config.minDuration) {
      // 达到最大持续时间,强制切换
      if (this.currentStageDuration >= config.maxDuration) {
        this.transitionToNextStage();
        return;
      }

      // 随机概率切换
      if (Math.random() < config.transitionChance) {
        this.transitionToNextStage();
      }
    }
  }

  // 切换到下一睡眠阶段
  transitionToNextStage() {
    const now = new Date();

    // 保存当前阶段
    this.saveCurrentStage(now);

    // 决定下一阶段
    let nextStage: SleepStageType;

    switch (this.currentStage) {
      case SleepStageType.AWAKE:
        nextStage = Math.random() < 0.8 ? SleepStageType.LIGHT : SleepStageType.DEEP;
        break;
      case SleepStageType.LIGHT:
        nextStage = Math.random() < 0.6 ? SleepStageType.DEEP : SleepStageType.AWAKE;
        break;
      case SleepStageType.DEEP:
        nextStage = Math.random() < 0.8 ? SleepStageType.LIGHT : SleepStageType.AWAKE;
        break;
    }

    // 更新当前阶段
    this.currentStage = nextStage;
    this.currentStageStartTime = now;
    this.currentStageDuration = 0;
  }

  // 保存当前睡眠阶段
  saveCurrentStage(endTime: Date) {
    const config = this.stageConfig[this.currentStage];
    const duration = this.currentStageDuration;

    // 计算小时和分钟
    const hours = Math.floor(duration / 3600);
    const minutes = Math.floor((duration % 3600) / 60);
    const seconds = duration % 60;

    // 格式化持续时间
    let durationStr = '';
    if (hours > 0) durationStr += `${hours}小时`;
    if (minutes > 0) durationStr += `${minutes}分`;
    if (seconds > 0 || durationStr === '') durationStr += `${seconds}秒`;

    // 添加到睡眠阶段列表
    this.sleepStages.push({
      type: this.currentStage,
      icon: config.icon,
      title: config.title,
      duration: durationStr,
      color: config.color,
      startTime: new Date(this.currentStageStartTime),
      endTime: new Date(endTime),
      percentage: "0%"
    });
  }

  // 计算各阶段百分比
  calculateStagePercentages() {
    const totalSeconds = this.getTotalSeconds(this.sleepDuration);

    if (totalSeconds === 0) return;

    for (const stage of this.sleepStages) {
      const stageSeconds = (stage.endTime.getTime() - stage.startTime.getTime()) / 1000;
      const percentage = Math.round((stageSeconds / totalSeconds) * 100);
      stage.percentage = `${percentage}%`;
    }
  }

  // 计算睡眠质量评分
  calculateQualityScore(): number {
    let score = 50; // 基础分

    // 深度睡眠占比越高,分数越高
    const deepStage = this.sleepStages.find(s => s.type === SleepStageType.DEEP);
    if (deepStage) {
      const deepSeconds = (deepStage.endTime.getTime() - deepStage.startTime.getTime()) / 1000;
      const totalSeconds = this.getTotalSeconds(this.sleepDuration);
      const deepPercentage = (deepSeconds / totalSeconds) * 100;

      // 深度睡眠占比20%-30%为正常,每超出1%加1分,不足1%减1分
      if (deepPercentage > 30) {
        score += Math.min(20, Math.round(deepPercentage - 30));
      } else if (deepPercentage < 20) {
        score -= Math.min(20, Math.round(20 - deepPercentage));
      }
    }

    // 清醒阶段越少,分数越高
    const awakeStage = this.sleepStages.find(s => s.type === SleepStageType.AWAKE);
    if (awakeStage) {
      const awakeSeconds = (awakeStage.endTime.getTime() - awakeStage.startTime.getTime()) / 1000;
      const totalSeconds = this.getTotalSeconds(this.sleepDuration);
      const awakePercentage = (awakeSeconds / totalSeconds) * 100;

      // 清醒占比10%以下为正常,每超出1%减2分
      if (awakePercentage > 10) {
        score -= Math.min(30, Math.round((awakePercentage - 10) * 2));
      }
    }

    // 睡眠周期越多,分数越高
    const cycleCount = Math.floor(this.sleepStages.length / 3); // 每3个阶段算一个周期
    score += Math.min(10, cycleCount * 2);

    // 确保分数在0-100范围内
    return Math.max(0, Math.min(100, score));
  }

  // 计算时间差
  calculateDuration(start: Date, end: Date): string {
    const diffMs = end.getTime() - start.getTime();
    const hours = Math.floor(diffMs / (1000 * 60 * 60));
    const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);

    return `${hours}时${minutes}分${seconds}秒`;
  }

  // 格式化时间
  formatTime(date: Date): string {
    return `${date.getHours().toString().padStart(2, '0')}:${
    date.getMinutes().toString().padStart(2, '0')}:${
    date.getSeconds().toString().padStart(2, '0')
    }`;
  }

  // 获取总秒数
  getTotalSeconds(duration: string): number {
    const parts = duration.split('时');
    const hourPart = parts[0];
    const minutePart = parts[1].split('分')[0];
    const secondPart = parts[1].split('分')[1].split('秒')[0];

    const hours = Number(hourPart);
    const minutes = Number(minutePart);
    const seconds = Number(secondPart || 0);

    return hours * 3600 + minutes * 60 + seconds;
  }

  build() {
    Column({ space: 15 }) {
      // 睡眠状态卡片
      Column({ space: 20 }) {
        Stack() {
          // 圆形进度条
          Circle()
            .fill(this.isSleeping ? '#3498db' : '#4682b4')
            .width(200)
            .height(200)
            .opacity(0.9);

          Column({ space: 8 }) {
            Text(this.sleepDuration)
              .fontSize(32)
              .fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold);

            Text(`开始时间: ${this.formatTime(this.startTime)}`)
              .fontSize(14)
              .fontColor('#FFFFFF');

            Text(`结束时间: ${this.endTimeStr}`)
              .fontSize(14)
              .fontColor('#FFFFFF');

            if (this.isSleeping) {
              Text(`当前: ${this.stageConfig[this.currentStage].title}`)
                .fontSize(14)
                .fontColor('#FFFFFF')
                .backgroundColor(this.stageConfig[this.currentStage].color + '50')
                .padding(5)
                .borderRadius(5);
            }
          }
        }
        .margin({ top: 20 });

        // 睡眠控制按钮
        Button(this.isSleeping ? '起床' : '开始睡觉')
          .width(150)
          .height(50)
          .backgroundColor('#FFFFFF')
          .fontColor(this.isSleeping ? '#e74c3c' : '#3498db')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.isSleeping ? this.endSleep() : this.startSleep();
          });
      }
      .width('100%')
      .height(this.isSleeping ? 350 : 320)
      .backgroundColor(this.isSleeping ? '#3498db' : '#4682b4')
      .borderRadius(20)
      .shadow({ radius: 5, color: '#0000001A' });

      // 睡眠阶段展示
      Text('睡眠阶段分析')
        .fontSize(18)
        .fontColor('#333')
        .fontWeight(FontWeight.Bold)
        .margin({ top: 10, bottom: 15 });

      // 睡眠阶段统计
      if (!this.isSleeping && this.sleepStages.length > 0) {
        Row({ space: 15 }) {
          ForEach(this.sleepStages, (stage: SleepStage) => {
            Column({ space: 8 }) {
              Image(stage.icon)
                .width(60)
                .height(60)
                .backgroundColor(stage.color + '10')
                .borderRadius(10);

              Text(stage.title)
                .fontSize(16)
                .fontColor('#333333')
                .fontWeight(FontWeight.Medium);

              Text(stage.duration)
                .fontSize(14)
                .fontColor('#666666');

              Text(stage.percentage)
                .fontSize(12)
                .fontColor(stage.color)
                .backgroundColor(stage.color + '10')
                .padding(5)
                .borderRadius(10);
            }
            .flexGrow(1)
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center);
          });
        }
        .width('100%')
        .padding({ left: 15, right: 15 });

        // 睡眠质量评分
        if (this.sleepRecords.length > 0) {
          Column() {
            Text(`睡眠质量评分: ${this.sleepRecords[0].qualityScore}/100`)
              .fontSize(16)
              .fontColor('#333')
              .fontWeight(FontWeight.Medium);

            Text(this.getQualityDescription(this.sleepRecords[0].qualityScore))
              .fontSize(14)
              .fontColor('#666')
              .margin({ top: 5 });
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#f5f7fa')
          .borderRadius(15)
          .margin({ top: 15 });
        }
      } else {
        Text(this.isSleeping ? '睡眠中,阶段数据将在结束后显示' : '暂无睡眠阶段数据')
          .fontSize(14)
          .fontColor('#666')
          .width('100%')
          .textAlign(TextAlign.Center)
          .margin({ top: 15 });
      }
    }
    .height('100%')
    .padding(15);
  }

  // 根据睡眠质量评分获取描述
  getQualityDescription(score: number): string {
    if (score >= 90) return '优秀:睡眠质量极佳,身心充分恢复';
    if (score >= 75) return '良好:睡眠质量不错,身体得到较好休息';
    if (score >= 60) return '一般:睡眠质量一般,建议调整作息';
    if (score >= 40) return '较差:睡眠质量不佳,可能影响日常状态';
    return '很差:睡眠质量严重不足,建议就医检查';
  }
}