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

智能睡眠监测应用

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

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

睡眠阶段枚举(SleepStageType定义清醒(AWAKE)、浅度(LIGHT)、深度(DEEP)三种核心状态,为睡眠周期分析建立分类标准: 

enum SleepStageType {

  AWAKE = 'awake',

  LIGHT = 'light',

  DEEP = 'deep'

}

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

interface SleepSTyeS {

  icon: string;          // 阶段图标

  title: string;        // 阶段名称

  color: string;        // 对应颜色

  minDuration: number;  // 最小持续时间(秒)

  maxDuration: number;  // 最大持续时间(秒)

  transitionChance: number; // 切换概率

}

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

interface SleepStage {

  type: SleepStageType;  // 阶段类型

  icon: string;          // 图标路径

  title: string;        // 阶段标题

  duration: string;      // 持续时长

  color: string;        // 阶段颜色

  startTime: Date;      // 开始时间

  endTime: Date;        // 结束时间

  percentage: string;    // 时长占比

}

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

interface SleepRecord {

  id: string;            // 记录ID

  startTime: Date;      // 开始时间

  endTime: Date;        // 结束时间

  duration: string;      // 总时长

  stages: SleepStage[];  // 阶段列表

  qualityScore: number;  // 质量评分(0-100)

}

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

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

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

计时器驱动的状态更新通过setInterval实现1秒精度的时间追踪,同时触发阶段检查: 

startSleep() {

  this.timerId = setInterval(() => {

    this.sleepDuration = this.calculateDuration(this.startTime, new Date());

    this.currentStageDuration++;

    this.checkStageTransition();  // 检查阶段切换

  }, 1000);

}

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

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%概率进入深度睡眠) 

超过最大持续时间则强制切换,模拟真实睡眠周期规律

状态流转控制通过定义阶段转换规则,模拟睡眠周期的自然波动: 

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分),算法实现兼顾专业性与可解释性: 

深度睡眠占比分析以深度睡眠占比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;

}

清醒状态惩罚机制清醒占比超过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个阶段(约一个完整睡眠周期)加2分,最多加10分: 

const cycleCount = Math.floor(this.sleepStages.length / 3);

score += Math.min(10, cycleCount * 2);

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

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

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

睡眠状态主卡片 

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);

    }

  }

}

圆形进度条背景(蓝色表示睡眠中,深蓝色表示已结束) 

实时显示睡眠时长、阶段状态及时间信息 

状态切换按钮通过颜色区分操作(绿色"开始"、红色"起床")

阶段分析面板 

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 '很差:睡眠质量严重不足,建议就医检查';

  }

}

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容