在这里插入图片描述

说起睡眠,这是我最关心的健康指标之一。以前总是熬夜,第二天精神很差,工作效率也低。后来开始记录睡眠时间,发现自己的睡眠质量真的很糟糕。有了数据支撑,我才下决心改善作息。所以在做这个生活助手App时,睡眠记录是我最想实现的功能之一。

为什么睡眠记录这么重要

在开始写代码之前,我先想清楚了这个功能的价值。

第一个是健康意识。很多人不知道自己睡得够不够,通过记录睡眠时间,可以直观地看到自己的睡眠状况。成年人每天应该睡7-9小时,如果长期睡眠不足,对健康影响很大。

第二个是作息调整。通过查看睡眠趋势,可以发现作息规律的问题。比如发现自己周末总是睡得很晚,工作日又起得很早,这种不规律的作息对身体不好。

第三个是睡眠质量。不仅要看睡了多久,还要看睡得好不好。深度睡眠、浅度睡眠、REM睡眠的比例,都会影响睡眠质量。

页面整体设计

睡眠记录页面要给人一种宁静的感觉,我用了深色系的渐变:

class SleepTrackerPage extends StatelessWidget {
  const SleepTrackerPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('睡眠记录'),
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildSleepSummary(),
            SizedBox(height: 24.h),
            _buildWeeklyChart(),
            SizedBox(height: 24.h),
            _buildSleepQuality(),
          ],
        ),
      ),
    );
  }
}

为什么用StatelessWidget

这个页面只是展示数据,没有用户交互导致的状态变化,所以用StatelessWidget就够了。数据从外部传入或从数据库读取。

页面布局的三个部分

页面分成三块:睡眠总结卡片、本周趋势图表、睡眠质量详情。这个顺序是经过考虑的,从总览到详细,符合用户的阅读习惯。

睡眠总结卡片

页面顶部是一个醒目的睡眠总结卡片:

Widget _buildSleepSummary() {
  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      gradient: const LinearGradient(
        colors: [Colors.indigo, Colors.purple],
      ),
      borderRadius: BorderRadius.circular(16.r),
    ),
    child: Column(
      children: [
        Text(
          '昨晚睡眠',
          style: TextStyle(color: Colors.white70, fontSize: 14.sp),
        ),
        SizedBox(height: 8.h),
        Text(
          '7小时30分',
          style: TextStyle(
            color: Colors.white,
            fontSize: 36.sp,
            fontWeight: FontWeight.bold,
          ),
        ),

渐变色的选择

我用了靛蓝色到紫色的渐变,这两种颜色给人一种夜晚、宁静的感觉。和睡眠的主题很搭配。如果用亮色系,会显得太刺眼,不符合睡眠的氛围。

睡眠时长的展示

睡眠时长用36sp的大字号显示,非常醒目。这是用户最关心的数据,所以要放在最显眼的位置。

入睡和起床时间

SizedBox(height: 16.h),
Row(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: [
    _buildTimeInfo('入睡', '23:00'),
    _buildTimeInfo('起床', '06:30'),
  ],
),

除了总时长,还要显示入睡和起床的具体时间。这样用户可以知道自己是几点睡的,几点起的。有些人睡得够久,但睡得太晚,这也不健康。

时间信息的组件

Widget _buildTimeInfo(String label, String time) {
  return Column(
    children: [
      Text(
        label,
        style: TextStyle(color: Colors.white70, fontSize: 12.sp),
      ),
      SizedBox(height: 4.h),
      Text(
        time,
        style: TextStyle(
          color: Colors.white,
          fontSize: 18.sp,
          fontWeight: FontWeight.bold,
        ),
      ),
    ],
  );
}

标签用半透明的白色,时间用纯白色加粗,形成层次关系。这种设计让信息更清晰。

本周睡眠趋势图

趋势图让用户看到一周的睡眠变化:

Widget _buildWeeklyChart() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '本周睡眠趋势',
          style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 20.h),
        SizedBox(
          height: 200.h,
          child: BarChart(
            BarChartData(
              alignment: BarChartAlignment.spaceAround,
              maxY: 10,
              barTouchData: BarTouchData(enabled: false),

为什么用柱状图

睡眠时长用柱状图展示最合适。柱子的高度直观地表示睡眠时长,用户一眼就能看出哪天睡得多,哪天睡得少。

Y轴最大值的设置

maxY: 10表示Y轴最大值是10小时。这个值要根据实际情况调整,如果用户睡眠时间都在7-8小时,设置成10小时刚好。如果设置太大,柱子会显得很矮;设置太小,柱子会顶到天花板。

底部标签的实现

titlesData: FlTitlesData(
  show: true,
  bottomTitles: AxisTitles(
    sideTitles: SideTitles(
      showTitles: true,
      getTitlesWidget: (value, meta) {
        const days = ['一', '二', '三', '四', '五', '六', '日'];
        if (value.toInt() >= 0 && value.toInt() < days.length) {
          return Text(days[value.toInt()], style: TextStyle(fontSize: 12.sp));
        }
        return const Text('');
      },
    ),
  ),
  leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
  topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
  rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),

只显示底部的星期标签,其他轴的标签都隐藏。这样图表更简洁,用户不会被太多信息干扰。

柱子数据的构建

barGroups: [
  BarChartGroupData(x: 0, barRods: [BarChartRodData(toY: 7, color: Colors.purple)]),
  BarChartGroupData(x: 1, barRods: [BarChartRodData(toY: 6.5, color: Colors.purple)]),
  BarChartGroupData(x: 2, barRods: [BarChartRodData(toY: 8, color: Colors.purple)]),
  BarChartGroupData(x: 3, barRods: [BarChartRodData(toY: 7.5, color: Colors.purple)]),
  BarChartGroupData(x: 4, barRods: [BarChartRodData(toY: 6, color: Colors.purple)]),
  BarChartGroupData(x: 5, barRods: [BarChartRodData(toY: 8.5, color: Colors.purple)]),
  BarChartGroupData(x: 6, barRods: [BarChartRodData(toY: 7.5, color: Colors.purple)]),
],

每个柱子用BarChartGroupData表示,x是位置(星期几),toY是高度(睡眠时长)。所有柱子都用紫色,和顶部卡片的主题色保持一致。

从数据可以看出,周五睡得最少(6小时),周六睡得最多(8.5小时)。这是很常见的模式,工作日睡得少,周末补觉。

睡眠质量详情

睡眠质量比睡眠时长更重要,我用进度条展示各个阶段的睡眠:

Widget _buildSleepQuality() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '睡眠质量',
          style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 16.h),
        _buildQualityItem('深度睡眠', '2小时15分', 0.3, Colors.indigo),
        SizedBox(height: 12.h),
        _buildQualityItem('浅度睡眠', '4小时30分', 0.6, Colors.blue),
        SizedBox(height: 12.h),
        _buildQualityItem('REM睡眠', '45分钟', 0.1, Colors.purple),
      ],
    ),
  );
}

三种睡眠阶段

  • 深度睡眠:最重要的睡眠阶段,身体和大脑得到充分休息。用深靛蓝色表示。
  • 浅度睡眠:占比最大的睡眠阶段,身体放松但容易被唤醒。用蓝色表示。
  • REM睡眠:快速眼动睡眠,做梦的阶段,对记忆巩固很重要。用紫色表示。

质量项的实现

Widget _buildQualityItem(String label, String duration, double percent, Color color) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label, style: TextStyle(fontSize: 14.sp)),
          Text(duration, style: TextStyle(fontSize: 12.sp, color: Colors.grey)),
        ],
      ),
      SizedBox(height: 8.h),
      LinearProgressIndicator(
        value: percent,
        backgroundColor: Colors.grey[200],
        color: color,
        minHeight: 8.h,
        borderRadius: BorderRadius.circular(4.r),
      ),
    ],
  );
}

每个睡眠阶段显示时长和占比。进度条的长度表示占比,颜色表示睡眠类型。这种可视化比纯数字更直观。

睡眠数据的计算

实际项目中,睡眠数据需要从传感器或手动输入获取:

class SleepRecord {
  final DateTime bedTime;
  final DateTime wakeTime;
  final Duration deepSleep;
  final Duration lightSleep;
  final Duration remSleep;
  
  SleepRecord({
    required this.bedTime,
    required this.wakeTime,
    required this.deepSleep,
    required this.lightSleep,
    required this.remSleep,
  });
  
  Duration get totalSleep => wakeTime.difference(bedTime);
  
  String get totalSleepFormatted {
    final hours = totalSleep.inHours;
    final minutes = totalSleep.inMinutes % 60;
    return '$hours小时$minutes分';
  }
  
  double get deepSleepPercent => deepSleep.inMinutes / totalSleep.inMinutes;
  double get lightSleepPercent => lightSleep.inMinutes / totalSleep.inMinutes;
  double get remSleepPercent => remSleep.inMinutes / totalSleep.inMinutes;
}

定义一个SleepRecord类来表示睡眠记录,包含入睡时间、起床时间、各阶段睡眠时长。提供一些计算属性,方便获取总时长和各阶段占比。

手动记录睡眠

用户可以手动输入睡眠时间:

class AddSleepRecordPage extends StatefulWidget {
  const AddSleepRecordPage({super.key});

  
  State<AddSleepRecordPage> createState() => _AddSleepRecordPageState();
}

class _AddSleepRecordPageState extends State<AddSleepRecordPage> {
  DateTime? _bedTime;
  DateTime? _wakeTime;
  
  Future<void> _selectBedTime() async {
    final date = await showDatePicker(
      context: context,
      initialDate: DateTime.now().subtract(const Duration(days: 1)),
      firstDate: DateTime.now().subtract(const Duration(days: 30)),
      lastDate: DateTime.now(),
    );
    
    if (date != null) {
      final time = await showTimePicker(
        context: context,
        initialTime: const TimeOfDay(hour: 23, minute: 0),
      );
      
      if (time != null) {
        setState(() {
          _bedTime = DateTime(
            date.year,
            date.month,
            date.day,
            time.hour,
            time.minute,
          );
        });
      }
    }
  }

用户先选择日期,再选择时间。这样可以记录过去几天的睡眠数据。

数据验证

Future<void> _saveSleepRecord() async {
  if (_bedTime == null || _wakeTime == null) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('请选择入睡和起床时间')),
    );
    return;
  }
  
  if (_wakeTime!.isBefore(_bedTime!)) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('起床时间不能早于入睡时间')),
    );
    return;
  }
  
  final record = SleepRecord(
    bedTime: _bedTime!,
    wakeTime: _wakeTime!,
    deepSleep: Duration.zero,
    lightSleep: Duration.zero,
    remSleep: Duration.zero,
  );
  
  await saveSleepRecord(record);
  
  if (mounted) {
    Navigator.pop(context);
  }
}

保存前要验证:时间不能为空,起床时间不能早于入睡时间。这些都是基本的数据验证。

睡眠质量评分

根据睡眠数据计算质量评分:

int calculateSleepScore(SleepRecord record) {
  int score = 0;
  
  // 总时长评分(最高40分)
  final hours = record.totalSleep.inHours;
  if (hours >= 7 && hours <= 9) {
    score += 40;
  } else if (hours >= 6 && hours < 7) {
    score += 30;
  } else if (hours >= 5 && hours < 6) {
    score += 20;
  } else {
    score += 10;
  }
  
  // 深度睡眠评分(最高30分)
  final deepPercent = record.deepSleepPercent;
  if (deepPercent >= 0.2) {
    score += 30;
  } else if (deepPercent >= 0.15) {
    score += 20;
  } else {
    score += 10;
  }
  
  // REM睡眠评分(最高30分)
  final remPercent = record.remSleepPercent;
  if (remPercent >= 0.2) {
    score += 30;
  } else if (remPercent >= 0.15) {
    score += 20;
  } else {
    score += 10;
  }
  
  return score;
}

评分考虑三个因素:总时长、深度睡眠占比、REM睡眠占比。满分100分,7-9小时睡眠、深度睡眠占比20%以上、REM睡眠占比20%以上,可以得满分。

质量等级

String getSleepQualityLevel(int score) {
  if (score >= 90) return '优秀';
  if (score >= 75) return '良好';
  if (score >= 60) return '一般';
  return '较差';
}

根据分数给出质量等级,让用户更直观地了解睡眠质量。

睡眠建议

根据睡眠数据给出个性化建议:

String getSleepAdvice(SleepRecord record) {
  final hours = record.totalSleep.inHours;
  
  if (hours < 6) {
    return '睡眠时间严重不足,建议早点休息,保证每天至少7小时睡眠。';
  }
  
  if (hours > 9) {
    return '睡眠时间过长,可能影响白天精神状态,建议调整作息。';
  }
  
  if (record.deepSleepPercent < 0.15) {
    return '深度睡眠不足,建议睡前避免使用电子设备,保持卧室安静。';
  }
  
  if (record.bedTime.hour > 23 || record.bedTime.hour < 6) {
    return '入睡时间较晚,建议在晚上11点前入睡,有助于提高睡眠质量。';
  }
  
  return '睡眠质量不错,继续保持良好的作息习惯!';
}

根据不同的睡眠问题给出针对性建议。这些建议要实用,不要太空泛。

实际使用体验

我自己用了一段时间睡眠记录功能,确实帮助我改善了作息。以前总是熬夜到凌晨一两点,看到睡眠数据后,发现自己的睡眠质量真的很差。

通过查看本周趋势图,我发现自己工作日睡得很少,周末又睡得很多。这种不规律的作息对身体不好。后来我开始调整,尽量每天都在11点前睡觉,早上7点起床。坚持了一段时间,感觉精神状态好多了。

睡眠质量的详细数据也很有用。我发现自己深度睡眠占比很低,后来睡前不玩手机,保持卧室安静,深度睡眠时间明显增加了。

可以改进的地方

如果要做得更完善,可以考虑以下几点。

智能手环集成

接入智能手环或手表的数据,自动记录睡眠。这样用户不用手动输入,数据也更准确。

睡眠环境监测

记录睡眠时的环境因素,比如温度、湿度、噪音等。分析这些因素对睡眠质量的影响。

睡眠音乐

提供助眠音乐或白噪音,帮助用户更快入睡。可以设置定时关闭,避免影响睡眠。

睡眠报告

生成周报或月报,总结睡眠情况,给出改善建议。可以导出PDF,方便用户查看或分享给医生。

小结

睡眠记录功能看起来简单,但对健康管理很重要。通过记录和分析睡眠数据,用户可以更好地了解自己的睡眠状况,发现问题,改善作息。

在实现过程中,我特别注重数据的可视化。趋势图、进度条、评分等级,这些都是为了让用户更直观地了解睡眠情况。数字本身是冰冷的,但通过合适的展示方式,可以让数据变得有温度。

从我自己的使用体验来看,这个功能确实帮助我改善了睡眠质量。希望这篇文章能给你带来一些启发。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐