鸿蒙Flutter实战:构建智能健康管理应用

一、项目概述:智能健康助手

随着健康意识的提升,人们对个人健康数据的管理需求日益增长。本项目将使用鸿蒙Flutter构建一个支持多设备协同的智能健康管理应用,通过与华为穿戴设备、智能体脂秤等设备的无缝连接,提供全面的健康数据监控和管理。

核心功能特性:

📊 健康数据同步
  • 支持主流穿戴设备(如智能手表、手环)数据自动采集
  • 实时同步心率、步数、睡眠等15+项健康指标
  • 采用华为HiHealth协议保障数据传输稳定性
  • 示例:华为Watch GT3数据3秒内完成同步
📱 多端查看
  • 手机端:完整功能+快捷入口
  • 平板端:大屏可视化数据看板
  • 智慧屏:家庭健康数据共享模式
  • 支持EMUI/HarmonyOS多系统协同
🔄 分布式存储
  • 采用256位AES加密存储
  • 云端+本地双备份机制
  • 支持历史数据追溯(最长5年)
  • 符合GDPR数据安全标准
🏃‍♂️ 运动指导
  • 基于用户BMI和运动习惯的智能推荐
  • 提供200+种运动课程(室内/户外)
  • 实时运动数据监测和语音指导
  • 场景示例:为久坐上班族推荐"办公室拉伸计划"
🍎 饮食建议
  • 接入百万级食物营养数据库
  • 智能分析每日摄入营养缺口
  • 支持扫码识别食品营养成分
  • 示例:针对高血压患者推荐低钠食谱
📈 健康报告
  • 周/月/季度多维健康评估
  • 12项专业健康指标分析
  • 可视化趋势图表+改善建议
  • 支持PDF导出分享给医生### 核心功能特性:
📊 健康数据同步
  • 支持主流穿戴设备(如智能手表、手环)数据自动采集
  • 实时同步心率、步数、睡眠等15+项健康指标
  • 采用华为HiHealth协议保障数据传输稳定性
  • 示例:华为Watch GT3数据3秒内完成同步
📱 多端查看
  • 手机端:完整功能+快捷入口
  • 平板端:大屏可视化数据看板
  • 智慧屏:家庭健康数据共享模式
  • 支持EMUI/HarmonyOS多系统协同
🔄 分布式存储
  • 采用256位AES加密存储
  • 云端+本地双备份机制
  • 支持历史数据追溯(最长5年)
  • 符合GDPR数据安全标准
🏃‍♂️ 运动指导
  • 基于用户BMI和运动习惯的智能推荐
  • 提供200+种运动课程(室内/户外)
  • 实时运动数据监测和语音指导
  • 场景示例:为久坐上班族推荐"办公室拉伸计划"
🍎 饮食建议
  • 接入百万级食物营养数据库
  • 智能分析每日摄入营养缺口
  • 支持扫码识别食品营养成分
  • 示例:针对高血压患者推荐低钠食谱
📈 健康报告
  • 周/月/季度多维健康评估
  • 12项专业健康指标分析
  • 可视化趋势图表+改善建议
  • 支持PDF导出分享给医生

二、项目架构设计

2.1 技术选型

# pubspec.yaml 核心依赖
dependencies:
  flutter:
    sdk: flutter
  
  # 鸿蒙健康服务
  harmony_health: ^1.0.0  # 健康数据访问
  harmony_wearable: ^0.8.0  # 穿戴设备连接
  harmony_sensors: ^1.1.0  # 传感器数据
  
  # 数据可视化
  fl_chart: ^0.60.0  # 图表绘制
  syncfusion_flutter_charts: ^20.0.0  # 高级图表
  
  # 状态管理
  riverpod: ^2.0.0  # Riverpod状态管理
  riverpod_hooks: ^2.0.0  # Riverpod Hooks
  
  # 网络请求
  retrofit: ^4.0.0  # REST API客户端
  logger: ^1.1.0  # 日志记录
  
  # 本地存储
  objectbox: ^2.0.0  # 本地数据库
  isar: ^3.1.0  # 高性能数据库
  
  # UI组件
  sliding_up_panel: ^2.0.0  # 滑动面板
  timeline_tile: ^2.0.0  # 时间线组件
  flutter_animate: ^4.0.0  # 动画效果
  
  # 工具类
  intl: ^0.18.0  # 国际化
  permission_handler: ^10.0.0  # 权限管理

2.2 项目结构

health_manager_harmony/
├── lib/
│   ├── main.dart                    # 应用入口
│   ├── app/                         # 应用配置
│   │   ├── app.dart
│   │   ├── router.dart              # 路由管理
│   │   └── theme.dart               # 健康主题
│   ├── common/                      # 通用组件
│   │   ├── widgets/                 # 通用Widget
│   │   ├── charts/                  # 图表组件
│   │   └── utils/                   # 工具函数
│   ├── features/                    # 功能模块
│   │   ├── dashboard/               # 健康仪表板
│   │   ├── activity/                # 运动记录
│   │   ├── sleep/                   # 睡眠分析
│   │   ├── nutrition/               # 营养管理
│   │   ├── devices/                 # 设备管理
│   │   └── reports/                 # 健康报告
│   ├── services/                    # 服务层
│   │   ├── health_service.dart      # 健康数据服务
│   │   ├── device_service.dart      # 设备服务
│   │   ├── ai_service.dart          # AI分析服务
│   │   └── sync_service.dart        # 数据同步服务
│   ├── models/                      # 数据模型
│   └── providers/                   # Riverpod提供者
└── harmony/                         # 鸿蒙配置
    ├── entry/                       # 应用入口
    └── service_widgets/             # 健康卡片

三、核心模块实现

3.1 健康仪表板模块

健康数据仪表板
// lib/features/dashboard/health_dashboard.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:harmony_health/harmony_health.dart';
import '../../providers/health_provider.dart';
import '../../common/charts/health_chart.dart';
import '../../models/health_model.dart';

class HealthDashboard extends ConsumerWidget {
  const HealthDashboard({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final healthData = ref.watch(healthDataProvider);
    final dailyGoal = ref.watch(dailyGoalProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('健康概况'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => ref.refresh(healthDataProvider),
          ),
          IconButton(
            icon: const Icon(Icons.insights),
            onPressed: () => _showHealthInsights(context, ref),
          ),
        ],
      ),
      body: RefreshIndicator(
        onRefresh: () async {
          ref.refresh(healthDataProvider);
          return ref.read(healthDataProvider.future);
        },
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              // 今日健康概览卡片
              _buildTodayOverview(healthData, dailyGoal),
              
              const SizedBox(height: 20),
              
              // 步数图表
              _buildStepsChart(healthData),
              
              const SizedBox(height: 20),
              
              // 心率图表
              _buildHeartRateChart(healthData),
              
              const SizedBox(height: 20),
              
              // 健康指标网格
              _buildHealthMetricsGrid(healthData),
              
              const SizedBox(height: 20),
              
              // 穿戴设备状态
              _buildDeviceStatus(ref),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTodayOverview(HealthData healthData, DailyGoal goal) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(20),
      ),
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Colors.blue.shade50,
              Colors.green.shade50,
            ],
          ),
          borderRadius: BorderRadius.circular(20),
        ),
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            const Text(
              '今日健康',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: Colors.blue,
              ),
            ),
            const SizedBox(height: 16),
            
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                // 步数
                _buildMetricCard(
                  icon: Icons.directions_walk,
                  value: '${healthData.steps}',
                  label: '步数',
                  progress: healthData.steps / goal.stepGoal,
                  color: Colors.blue,
                ),
                
                // 卡路里
                _buildMetricCard(
                  icon: Icons.local_fire_department,
                  value: '${healthData.calories}',
                  label: '卡路里',
                  progress: healthData.calories / goal.calorieGoal,
                  color: Colors.orange,
                ),
                
                // 活动时间
                _buildMetricCard(
                  icon: Icons.timer,
                  value: '${healthData.activeMinutes}',
                  label: '活动分钟',
                  progress: healthData.activeMinutes / goal.activeMinuteGoal,
                  color: Colors.green,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMetricCard({
    required IconData icon,
    required String value,
    required String label,
    required double progress,
    required Color color,
  }) {
    return Column(
      children: [
        Stack(
          alignment: Alignment.center,
          children: [
            SizedBox(
              width: 70,
              height: 70,
              child: CircularProgressIndicator(
                value: progress,
                strokeWidth: 8,
                backgroundColor: color.withOpacity(0.2),
                valueColor: AlwaysStoppedAnimation<Color>(color),
              ),
            ),
            Icon(icon, size: 30, color: color),
          ],
        ),
        const SizedBox(height: 8),
        Text(
          value,
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: color,
          ),
        ),
        Text(
          label,
          style: const TextStyle(fontSize: 12, color: Colors.grey),
        ),
      ],
    );
  }

  Widget _buildStepsChart(HealthData healthData) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '步数趋势',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              '过去7天平均 ${healthData.weeklyAvgSteps} 步',
              style: const TextStyle(color: Colors.grey),
            ),
            const SizedBox(height: 16),
            SizedBox(
              height: 200,
              child: HealthChart(
                data: healthData.weeklySteps,
                type: ChartType.bar,
                color: Colors.blue,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHeartRateChart(HealthData healthData) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '心率监测',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                _buildHeartRateIndicator(
                  '静息心率',
                  '${healthData.restingHeartRate}',
                  healthData.restingHeartRate < 70 ? Colors.green : Colors.orange,
                ),
                const SizedBox(width: 20),
                _buildHeartRateIndicator(
                  '最高心率',
                  '${healthData.maxHeartRate}',
                  Colors.red,
                ),
              ],
            ),
            const SizedBox(height: 16),
            SizedBox(
              height: 200,
              child: HealthChart(
                data: healthData.hourlyHeartRates,
                type: ChartType.line,
                color: Colors.red,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHeartRateIndicator(String label, String value, Color color) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          label,
          style: const TextStyle(color: Colors.grey, fontSize: 12),
        ),
        Row(
          children: [
            Icon(Icons.favorite, size: 16, color: color),
            const SizedBox(width: 4),
            Text(
              value,
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: color,
              ),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildHealthMetricsGrid(HealthData healthData) {
    return GridView.count(
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      crossAxisCount: 2,
      crossAxisSpacing: 12,
      mainAxisSpacing: 12,
      children: [
        _buildMetricTile(
          icon: Icons.bedtime,
          title: '睡眠质量',
          value: '${healthData.sleepScore}',
          unit: '分',
          color: Colors.purple,
        ),
        _buildMetricTile(
          icon: Icons.bloodtype,
          title: '血氧饱和度',
          value: '${healthData.bloodOxygen}',
          unit: '%',
          color: Colors.red,
        ),
        _buildMetricTile(
          icon: Icons.scale,
          title: '体重',
          value: healthData.weight.toStringAsFixed(1),
          unit: 'kg',
          color: Colors.green,
        ),
        _buildMetricTile(
          icon: Icons.water_drop,
          title: '水分摄入',
          value: '${healthData.waterIntake}',
          unit: 'ml',
          color: Colors.blue,
        ),
      ],
    );
  }

  Widget _buildMetricTile({
    required IconData icon,
    required String title,
    required String value,
    required String unit,
    required Color color,
  }) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: color.withOpacity(0.1),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(icon, color: color),
            ),
            const SizedBox(height: 12),
            Text(
              title,
              style: const TextStyle(
                fontSize: 14,
                color: Colors.grey,
              ),
            ),
            const SizedBox(height: 4),
            RichText(
              text: TextSpan(
                text: value,
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: color,
                ),
                children: [
                  TextSpan(
                    text: ' $unit',
                    style: const TextStyle(
                      fontSize: 14,
                      color: Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildDeviceStatus(WidgetRef ref) {
    final devices = ref.watch(connectedDevicesProvider);

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '已连接设备',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            if (devices.isEmpty)
              const Padding(
                padding: EdgeInsets.all(16),
                child: Center(
                  child: Text(
                    '未连接任何设备',
                    style: TextStyle(color: Colors.grey),
                  ),
                ),
              )
            else
              Column(
                children: devices.map((device) {
                  return ListTile(
                    leading: Icon(
                      _getDeviceIcon(device.type),
                      color: device.isConnected ? Colors.green : Colors.grey,
                    ),
                    title: Text(device.name),
                    subtitle: Text(
                      device.isConnected ? '已连接' : '已断开',
                      style: TextStyle(
                        color: device.isConnected ? Colors.green : Colors.grey,
                      ),
                    ),
                    trailing: device.batteryLevel != null
                        ? Chip(
                            label: Text('${device.batteryLevel}%'),
                            backgroundColor: _getBatteryColor(device.batteryLevel!),
                          )
                        : null,
                  );
                }).toList(),
              ),
            const SizedBox(height: 8),
            Center(
              child: TextButton.icon(
                onPressed: () => _scanDevices(ref),
                icon: const Icon(Icons.bluetooth),
                label: const Text('扫描设备'),
              ),
            ),
          ],
        ),
      ),
    );
  }

  IconData _getDeviceIcon(DeviceType type) {
    return switch (type) {
      DeviceType.watch => Icons.watch,
      DeviceType.band => Icons.fitness_center,
      DeviceType.scale => Icons.scale,
      DeviceType.thermometer => Icons.thermostat,
      _ => Icons.devices_other,
    };
  }

  Color _getBatteryColor(int batteryLevel) {
    if (batteryLevel > 60) return Colors.green.shade100;
    if (batteryLevel > 20) return Colors.orange.shade100;
    return Colors.red.shade100;
  }

  Future<void> _scanDevices(WidgetRef ref) async {
    try {
      await ref.read(deviceServiceProvider).scanForDevices();
      ref.refresh(connectedDevicesProvider);
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('扫描失败: $e')),
      );
    }
  }

  void _showHealthInsights(BuildContext context, WidgetRef ref) {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      builder: (context) {
        return HealthInsightsView(
          healthData: ref.read(healthDataProvider),
        );
      },
    );
  }
}
Riverpod状态管理
// lib/providers/health_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/health_service.dart';
import '../services/device_service.dart';
import '../models/health_model.dart';

// 健康数据提供者
final healthDataProvider = FutureProvider<HealthData>((ref) async {
  final healthService = ref.read(healthServiceProvider);
  return await healthService.getTodayHealthData();
});

// 周度数据提供者
final weeklyHealthDataProvider = FutureProvider<List<HealthData>>((ref) async {
  final healthService = ref.read(healthServiceProvider);
  return await healthService.getWeeklyHealthData();
});

// 每日目标提供者
final dailyGoalProvider = Provider<DailyGoal>((ref) {
  return DailyGoal(
    stepGoal: 10000,
    calorieGoal: 2000,
    activeMinuteGoal: 30,
    waterGoal: 2000,
  );
});

// 已连接设备提供者
final connectedDevicesProvider = FutureProvider<List<HealthDevice>>((ref) async {
  final deviceService = ref.read(deviceServiceProvider);
  return await deviceService.getConnectedDevices();
});

// 健康趋势提供者
final healthTrendProvider = FutureProvider<HealthTrend>((ref) async {
  final weeklyData = await ref.read(weeklyHealthDataProvider.future);
  return HealthTrend.calculateTrend(weeklyData);
});

// 服务提供者
final healthServiceProvider = Provider<HealthService>((ref) {
  return HealthService();
});

final deviceServiceProvider = Provider<DeviceService>((ref) {
  return DeviceService();
});

3.2 鸿蒙健康数据服务

// lib/services/health_service.dart
import 'package:harmony_health/harmony_health.dart';
import '../models/health_model.dart';

class HealthService {
  final HarmonyHealth _harmonyHealth = HarmonyHealth();
  final DateTime _now = DateTime.now();

  // 获取今日健康数据
  Future<HealthData> getTodayHealthData() async {
    try {
      // 从鸿蒙健康服务获取数据
      final steps = await _harmonyHealth.getSteps(
        startTime: _now.copyWith(hour: 0, minute: 0, second: 0),
        endTime: _now,
      );

      final heartRates = await _harmonyHealth.getHeartRates(
        startTime: _now.copyWith(hour: 0, minute: 0, second: 0),
        endTime: _now,
      );

      final sleepData = await _harmonyHealth.getSleepData(_now);

      final weightData = await _harmonyHealth.getWeightData(_now);

      // 转换为应用模型
      return HealthData(
        date: _now,
        steps: steps.totalSteps,
        calories: steps.calories,
        activeMinutes: steps.activeMinutes,
        restingHeartRate: heartRates.restingHeartRate,
        maxHeartRate: heartRates.maxHeartRate,
        hourlyHeartRates: heartRates.hourlyAverages,
        sleepScore: sleepData.sleepScore,
        sleepDuration: sleepData.duration,
        weight: weightData.weight,
        bloodOxygen: await _getLatestBloodOxygen(),
        waterIntake: await _getWaterIntake(),
      );
    } catch (e) {
      // 如果鸿蒙健康服务不可用,返回模拟数据
      return _getMockHealthData();
    }
  }

  // 获取周度健康数据
  Future<List<HealthData>> getWeeklyHealthData() async {
    final List<HealthData> weeklyData = [];

    for (int i = 6; i >= 0; i--) {
      final date = _now.subtract(Duration(days: i));
      try {
        final steps = await _harmonyHealth.getSteps(
          startTime: date.copyWith(hour: 0, minute: 0, second: 0),
          endTime: date.copyWith(hour: 23, minute: 59, second: 59),
        );

        weeklyData.add(
          HealthData(
            date: date,
            steps: steps.totalSteps,
            calories: steps.calories,
            activeMinutes: steps.activeMinutes,
            restingHeartRate: 72, // 模拟数据
            maxHeartRate: 125, // 模拟数据
            hourlyHeartRates: List.generate(24, (i) => 70 + i % 10),
            sleepScore: 85, // 模拟数据
            sleepDuration: const Duration(hours: 7, minutes: 30),
            weight: 65.0, // 模拟数据
            bloodOxygen: 98, // 模拟数据
            waterIntake: 1800, // 模拟数据
          ),
        );
      } catch (e) {
        // 添加空数据
        weeklyData.add(HealthData.empty(date));
      }
    }

    return weeklyData;
  }

  // 同步穿戴设备数据
  Future<void> syncWearableData() async {
    try {
      // 检查穿戴设备连接
      final wearables = await _harmonyHealth.getConnectedWearables();
      
      for (final wearable in wearables) {
        if (wearable.isConnected) {
          // 同步数据
          await _harmonyHealth.syncWearableData(wearable.id);
        }
      }
    } catch (e) {
      rethrow;
    }
  }

  // 写入健康数据
  Future<void> writeHealthData({
    required double weight,
    required int systolic,
    required int diastolic,
    required int bloodSugar,
  }) async {
    try {
      await _harmonyHealth.writeData(
        HealthDataType.weight,
        value: weight,
        unit: 'kg',
      );

      await _harmonyHealth.writeData(
        HealthDataType.bloodPressure,
        value: '$systolic/$diastolic',
        unit: 'mmHg',
      );

      await _harmonyHealth.writeData(
        HealthDataType.bloodSugar,
        value: bloodSugar.toDouble(),
        unit: 'mg/dL',
      );
    } catch (e) {
      rethrow;
    }
  }

  // 获取血氧数据
  Future<int> _getLatestBloodOxygen() async {
    try {
      final data = await _harmonyHealth.getLatestData(HealthDataType.bloodOxygen);
      return data.value.toInt();
    } catch (e) {
      return 98; // 默认值
    }
  }

  // 获取水分摄入数据
  Future<int> _getWaterIntake() async {
    try {
      final data = await _harmonyHealth.getLatestData(HealthDataType.waterIntake);
      return data.value.toInt();
    } catch (e) {
      return 1500; // 默认值
    }
  }

  // 模拟健康数据
  HealthData _getMockHealthData() {
    final random = DateTime.now().millisecond;

    return HealthData(
      date: _now,
      steps: 8234 + random % 2000,
      calories: 450 + random % 100,
      activeMinutes: 45 + random % 15,
      restingHeartRate: 68 + random % 8,
      maxHeartRate: 132 + random % 10,
      hourlyHeartRates: List.generate(24, (i) => 65 + i % 15 + random % 5),
      sleepScore: 82 + random % 15,
      sleepDuration: Duration(hours: 7, minutes: 30 + random % 30),
      weight: 65.0 + random % 100 / 10,
      bloodOxygen: 96 + random % 3,
      waterIntake: 1800 + random % 400,
    );
  }
}

3.3 睡眠分析模块

// lib/features/sleep/sleep_analysis.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:harmony_health/harmony_health.dart';
import '../../providers/sleep_provider.dart';
import '../../common/charts/sleep_chart.dart';

class SleepAnalysis extends ConsumerWidget {
  const SleepAnalysis({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final sleepData = ref.watch(sleepDataProvider);
    final sleepTrend = ref.watch(sleepTrendProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('睡眠分析'),
        actions: [
          IconButton(
            icon: const Icon(Icons.nightlight_round),
            onPressed: () => _setSleepGoal(context, ref),
          ),
        ],
      ),
      body: sleepData.when(
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => Center(child: Text('错误: $error')),
        data: (data) {
          return SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                // 睡眠评分卡片
                _buildSleepScoreCard(data),
                
                const SizedBox(height: 20),
                
                // 睡眠阶段图表
                _buildSleepStagesChart(data),
                
                const SizedBox(height: 20),
                
                // 睡眠趋势
                _buildSleepTrend(sleepTrend),
                
                const SizedBox(height: 20),
                
                // 睡眠建议
                _buildSleepRecommendations(data),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildSleepScoreCard(SleepData data) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(20),
      ),
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Colors.purple.shade50,
              Colors.blue.shade50,
            ],
          ),
          borderRadius: BorderRadius.circular(20),
        ),
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '睡眠评分',
                      style: TextStyle(
                        fontSize: 16,
                        color: Colors.grey,
                      ),
                    ),
                    Text(
                      '昨晚睡眠',
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.grey,
                      ),
                    ),
                  ],
                ),
                Text(
                  DateFormat('MM/dd').format(data.date),
                  style: const TextStyle(
                    fontSize: 16,
                    color: Colors.grey,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 20),
            
            // 睡眠评分环
            Stack(
              alignment: Alignment.center,
              children: [
                SizedBox(
                  width: 150,
                  height: 150,
                  child: CircularProgressIndicator(
                    value: data.score / 100,
                    strokeWidth: 12,
                    backgroundColor: Colors.purple.withOpacity(0.2),
                    valueColor: AlwaysStoppedAnimation<Color>(
                      _getSleepScoreColor(data.score),
                    ),
                  ),
                ),
                Column(
                  children: [
                    Text(
                      '${data.score}',
                      style: const TextStyle(
                        fontSize: 36,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      _getSleepQuality(data.score),
                      style: const TextStyle(
                        fontSize: 14,
                        color: Colors.grey,
                      ),
                    ),
                  ],
                ),
              ],
            ),
            const SizedBox(height: 20),
            
            // 睡眠详情
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                _buildSleepDetail(
                  icon: Icons.timer,
                  label: '睡眠时长',
                  value: _formatDuration(data.duration),
                ),
                _buildSleepDetail(
                  icon: Icons.nightlight,
                  label: '入睡时间',
                  value: DateFormat('HH:mm').format(data.sleepStart),
                ),
                _buildSleepDetail(
                  icon: Icons.wb_sunny,
                  label: '起床时间',
                  value: DateFormat('HH:mm').format(data.wakeUp),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSleepDetail({
    required IconData icon,
    required String label,
    required String value,
  }) {
    return Column(
      children: [
        Icon(icon, size: 24, color: Colors.purple),
        const SizedBox(height: 8),
        Text(
          label,
          style: const TextStyle(fontSize: 12, color: Colors.grey),
        ),
        Text(
          value,
          style: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
          ),
        ),
      ],
    );
  }

  Widget _buildSleepStagesChart(SleepData data) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '睡眠阶段',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              '深睡 ${data.deepSleepPercentage}% | '
              '浅睡 ${data.lightSleepPercentage}% | '
              'REM ${data.remSleepPercentage}%',
              style: const TextStyle(color: Colors.grey),
            ),
            const SizedBox(height: 16),
            SizedBox(
              height: 200,
              child: SleepChart(data: data),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSleepTrend(SleepTrend trend) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '睡眠趋势',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Icon(
                  trend.change > 0 ? Icons.trending_up : Icons.trending_down,
                  color: trend.change > 0 ? Colors.green : Colors.red,
                ),
                const SizedBox(width: 8),
                Text(
                  '较上周 ${trend.change > 0 ? '+' : ''}${trend.change.toStringAsFixed(1)}%',
                  style: TextStyle(
                    color: trend.change > 0 ? Colors.green : Colors.red,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            SizedBox(
              height: 150,
              child: SleepTrendChart(trend: trend),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSleepRecommendations(SleepData data) {
    final recommendations = _generateRecommendations(data);

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '睡眠建议',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            ...recommendations.map((rec) {
              return Padding(
                padding: const EdgeInsets.symmetric(vertical: 8),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Icon(
                      rec.icon,
                      color: rec.color,
                      size: 20,
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Text(
                        rec.text,
                        style: const TextStyle(fontSize: 14),
                      ),
                    ),
                  ],
                ),
              );
            }),
          ],
        ),
      ),
    );
  }

  String _formatDuration(Duration duration) {
    final hours = duration.inHours;
    final minutes = duration.inMinutes % 60;
    return '${hours}h ${minutes}m';
  }

  Color _getSleepScoreColor(int score) {
    if (score >= 90) return Colors.green;
    if (score >= 80) return Colors.blue;
    if (score >= 70) return Colors.orange;
    return Colors.red;
  }

  String _getSleepQuality(int score) {
    if (score >= 90) return '优秀';
    if (score >= 80) return '良好';
    if (score >= 70) return '一般';
    return '较差';
  }

  List<Recommendation> _generateRecommendations(SleepData data) {
    final recommendations = <Recommendation>[];

    // 睡眠时长建议
    if (data.duration.inHours < 7) {
      recommendations.add(
        Recommendation(
          icon: Icons.timer,
          text: '建议增加睡眠时长,目标是7-9小时',
          color: Colors.orange,
        ),
      );
    }

    // 深睡比例建议
    if (data.deepSleepPercentage < 20) {
      recommendations.add(
        Recommendation(
          icon: Icons.bedtime,
          text: '深睡比例偏低,建议保持规律作息',
          color: Colors.blue,
        ),
      );
    }

    // 入睡时间建议
    final sleepHour = data.sleepStart.hour;
    if (sleepHour > 23) {
      recommendations.add(
        Recommendation(
          icon: Icons.nightlight,
          text: '入睡时间较晚,建议23点前入睡',
          color: Colors.purple,
        ),
      );
    }

    return recommendations;
  }

  void _setSleepGoal(BuildContext context, WidgetRef ref) {
    showDialog(
      context: context,
      builder: (context) {
        return SleepGoalDialog(
          onGoalSet: (goal) {
            ref.read(sleepGoalProvider.notifier).setGoal(goal);
          },
        );
      },
    );
  }
}

class Recommendation {
  final IconData icon;
  final String text;
  final Color color;

  Recommendation({
    required this.icon,
    required this.text,
    required this.color,
  });
}

3.4 鸿蒙健康卡片

// harmony/service_widgets/health_quick_card.dart
import 'package:flutter/material.dart';
import 'package:harmony_service_card/harmony_service_card.dart';

class HealthQuickCard extends StatelessWidget {
  final int steps;
  final int heartRate;
  final int sleepScore;

  const HealthQuickCard({
    super.key,
    required this.steps,
    required this.heartRate,
    required this.sleepScore,
  });

  
  Widget build(BuildContext context) {
    return ServiceCard(
      width: 150,
      height: 150,
      updateInterval: const Duration(minutes: 5),
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Colors.blue.shade50,
              Colors.green.shade50,
            ],
          ),
          borderRadius: BorderRadius.circular(12),
        ),
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            // 标题
            const Text(
              '健康概览',
              style: TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.bold,
                color: Colors.blue,
              ),
            ),

            // 步数
            _buildHealthMetric(
              icon: Icons.directions_walk,
              value: steps.toString(),
              label: '步数',
              color: Colors.blue,
            ),

            // 心率
            _buildHealthMetric(
              icon: Icons.favorite,
              value: heartRate.toString(),
              label: '心率',
              color: Colors.red,
            ),

            // 睡眠评分
            _buildHealthMetric(
              icon: Icons.bedtime,
              value: '$sleepScore',
              label: '睡眠',
              color: Colors.purple,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHealthMetric({
    required IconData icon,
    required String value,
    required String label,
    required Color color,
  }) {
    return Row(
      children: [
        Icon(icon, size: 16, color: color),
        const SizedBox(width: 4),
        Expanded(
          child: Text(
            value,
            style: TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
              color: color,
            ),
          ),
        ),
        Text(
          label,
          style: const TextStyle(
            fontSize: 10,
            color: Colors.grey,
          ),
        ),
      ],
    );
  }
}

四、项目配置与优化

4.1 应用配置

// lib/app/app.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:harmony_health/harmony_health.dart';
import 'router.dart';
import 'theme.dart';

class MyApp extends ConsumerStatefulWidget {
  const MyApp({super.key});

  
  ConsumerState<MyApp> createState() => _MyAppState();
}

class _MyAppState extends ConsumerState<MyApp> {
  late HarmonyHealth _harmonyHealth;

  
  void initState() {
    super.initState();
    _initializeHealthKit();
  }

  Future<void> _initializeHealthKit() async {
    try {
      _harmonyHealth = HarmonyHealth();
      await _harmonyHealth.initialize();
      
      // 请求健康数据权限
      await _requestHealthPermissions();
    } catch (e) {
      print('HealthKit初始化失败: $e');
    }
  }

  Future<void> _requestHealthPermissions() async {
    final permissions = [
      HealthPermission.steps,
      HealthPermission.heartRate,
      HealthPermission.sleep,
      HealthPermission.weight,
      HealthPermission.bloodOxygen,
    ];

    await _harmonyHealth.requestPermissions(permissions);
  }

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: '健康助手',
      theme: lightTheme,
      darkTheme: darkTheme,
      routerConfig: router,
      debugShowCheckedModeBanner: false,
      builder: (context, child) {
        return MediaQuery(
          data: MediaQuery.of(context).copyWith(
            textScaleFactor: 1.0, // 防止系统字体缩放
          ),
          child: child!,
        );
      },
    );
  }
}

4.2 路由配置

// lib/app/router.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../features/dashboard/health_dashboard.dart';
import '../features/activity/activity_tracking.dart';
import '../features/sleep/sleep_analysis.dart';
import '../features/nutrition/nutrition_guide.dart';
import '../features/devices/device_management.dart';
import '../features/reports/health_reports.dart';

final router = GoRouter(
  initialLocation: '/dashboard',
  routes: [
    GoRoute(
      path: '/dashboard',
      name: 'dashboard',
      pageBuilder: (context, state) => CustomTransitionPage(
        key: state.pageKey,
        child: const HealthDashboard(),
        transitionsBuilder: (context, animation, secondaryAnimation, child) {
          return FadeTransition(opacity: animation, child: child);
        },
      ),
    ),
    GoRoute(
      path: '/activity',
      name: 'activity',
      pageBuilder: (context, state) => const MaterialPage(
        child: ActivityTracking(),
      ),
    ),
    GoRoute(
      path: '/sleep',
      name: 'sleep',
      pageBuilder: (context, state) => const MaterialPage(
        child: SleepAnalysis(),
      ),
    ),
    GoRoute(
      path: '/nutrition',
      name: 'nutrition',
      pageBuilder: (context, state) => const MaterialPage(
        child: NutritionGuide(),
      ),
    ),
    GoRoute(
      path: '/devices',
      name: 'devices',
      pageBuilder: (context, state) => const MaterialPage(
        child: DeviceManagement(),
      ),
    ),
    GoRoute(
      path: '/reports',
      name: 'reports',
      pageBuilder: (context, state) => const MaterialPage(
        child: HealthReports(),
      ),
    ),
  ],
);

4.3 数据模型

// lib/models/health_model.dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'health_model.freezed.dart';
part 'health_model.g.dart';


class HealthData with _$HealthData {
  const factory HealthData({
    required DateTime date,
    required int steps,
    required int calories,
    required int activeMinutes,
    required int restingHeartRate,
    required int maxHeartRate,
    required List<int> hourlyHeartRates,
    required int sleepScore,
    required Duration sleepDuration,
    required double weight,
    required int bloodOxygen,
    required int waterIntake,
  }) = _HealthData;

  factory HealthData.empty(DateTime date) => HealthData(
        date: date,
        steps: 0,
        calories: 0,
        activeMinutes: 0,
        restingHeartRate: 0,
        maxHeartRate: 0,
        hourlyHeartRates: [],
        sleepScore: 0,
        sleepDuration: Duration.zero,
        weight: 0.0,
        bloodOxygen: 0,
        waterIntake: 0,
      );

  factory HealthData.fromJson(Map<String, dynamic> json) =>
      _$HealthDataFromJson(json);
}


class SleepData with _$SleepData {
  const factory SleepData({
    required DateTime date,
    required int score,
    required Duration duration,
    required DateTime sleepStart,
    required DateTime wakeUp,
    required int deepSleepPercentage,
    required int lightSleepPercentage,
    required int remSleepPercentage,
    required int awakePercentage,
  }) = _SleepData;

  factory SleepData.fromJson(Map<String, dynamic> json) =>
      _$SleepDataFromJson(json);
}


class HealthDevice with _$HealthDevice {
  const factory HealthDevice({
    required String id,
    required String name,
    required DeviceType type,
    required bool isConnected,
    int? batteryLevel,
    DateTime? lastSynced,
  }) = _HealthDevice;

  factory HealthDevice.fromJson(Map<String, dynamic> json) =>
      _$HealthDeviceFromJson(json);
}

enum DeviceType {
  watch,
  band,
  scale,
  thermometer,
  bloodPressure,
  other,
}


class DailyGoal with _$DailyGoal {
  const factory DailyGoal({
    required int stepGoal,
    required int calorieGoal,
    required int activeMinuteGoal,
    required int waterGoal,
  }) = _DailyGoal;

  factory DailyGoal.fromJson(Map<String, dynamic> json) =>
      _$DailyGoalFromJson(json);
}

五、调试与发布

5.1 调试技巧

  1. 检查健康数据权限
# 查看健康数据权限状态
adb shell pm list permissions | grep health
  1. 模拟健康数据
// 开发模式下使用模拟数据
void main() {
  if (kDebugMode) {
    HealthService.useMockData = true;
  }
  runApp(const ProviderScope(child: MyApp()));
}
  1. 穿戴设备调试
# 查看连接的穿戴设备
hdc shell hidump -s 3303

5.2 构建发布

# 1. 构建Harmony应用
flutter build harmony --release --flavor prod

# 2. 检查健康权限配置
# 确保在config.json中正确声明健康权限
# {
#   "reqPermissions": [
#     {
#       "name": "ohos.permission.health",
#       "reason": "读取健康数据"
#     }
#   ]
# }

# 3. 测试分布式同步
# 在多台鸿蒙设备上安装测试同步功能

# 4. 发布到华为应用市场
# 通过DevEco Studio的发布向导完成

六、项目总结

✅ 核心技术点详解:

  1. 鸿蒙健康服务深度集成

    • 通过HarmonyOS Health Kit SDK实现穿戴设备数据接入
    • 支持心率、血氧、睡眠等12类健康数据的标准化采集
    • 示例:调用HealthKitManager.getLatestHeartRate()获取最新心率数据
    • 权限管理:需申请ohos.permission.health系列权限
  2. 分布式多设备数据同步

    • 基于HarmonyOS分布式数据管理能力
    • 实现手机、平板、智慧屏等多终端健康数据实时同步
    • 采用CRDT冲突解决算法保证数据一致性
    • 典型场景:运动时手表记录数据,回家后可在平板上查看
  3. 动态数据可视化引擎

    • 支持折线图/柱状图/雷达图等多种健康数据展示形式
    • 内置7天/30天/自定义时段的数据趋势分析
    • 使用Flutter的CustomPaint实现高性能渲染
    • 示例:睡眠质量图表展示深睡/浅睡/REM周期分布
  4. Riverpod状态管理方案

    • 采用StateNotifierProvider管理复杂健康数据流
    • 实现数据变更的自动监听和UI响应
    • 支持异步数据加载状态管理(loading/error/success)
    • 典型应用:全局健康档案的状态管理
  5. 服务卡片深度集成

    • 提供3种尺寸(2x2/2x4/4x4)的桌面卡片
    • 支持实时显示步数、心率等关键健康指标
    • 卡片点击可快速跳转至详情页
    • 开发要点:使用FormExtensionAbility实现卡片服务

📈 性能优化建议:

  1. 数据缓存:使用Isar数据库缓存历史数据

    • 实现本地数据持久化存储,减少网络请求次数
    • 示例:将用户最近30天的交易记录缓存在本地
    • 优势:离线可用,提升二次加载速度
    • 实现步骤:
      final isar = await Isar.open([TransactionSchema]);
      await isar.writeTxn(() async {
        await isar.transactions.putAll(transactions);
      });
      
  2. 按需同步:定时同步而不是实时同步

    • 设置合理的同步频率(如每15分钟)
    • 使用后台任务定期同步
    • 场景:对实时性要求不高的数据(如历史报表)
    • 实现方案:
      • Android使用WorkManager
      • iOS使用Background Fetch
      • Flutter可使用flutter_workmanager插件
  3. 图片资源优化:使用SVG格式的图表

    • 相比PNG/JPG,SVG具有:
      • 更小的文件体积
      • 无损缩放
      • 更好的显示效果
    • 适用场景:
      • 统计图表
      • 图标资源
      • 需要多分辨率适配的图片
    • 实现示例:
      SvgPicture.asset(
        'assets/charts/trend.svg',
        width: 200,
        height: 200,
      )
      
  4. 内存管理:及时清理历史数据

    • 定期清理策略:
      • 保留最近3个月数据
      • 自动删除过期缓存
    • 内存回收机制:
      void cleanOldData() async {
        final cutoff = DateTime.now().subtract(Duration(days: 90));
        await isar.writeTxn(() async {
          await isar.transactions
            .filter()
            .timestampLessThan(cutoff)
            .deleteAll();
        });
      }
      

    -监控工具:使用Dart DevTools分析内存使用情况

🚀 健康管理应用功能扩展

1. AI健康分析
  • 核心功能:采用机器学习算法分析用户健康数据
  • 数据分析:通过步数、心率、睡眠等数据建立健康模型
  • 智能建议
    • 每日生成个性化健康报告
    • 提供运动/休息建议(如:“今日心率偏高,建议减少高强度运动”)
    • 异常指标预警(血压/血糖异常提醒)
  • 案例:针对糖尿病患者自动分析血糖变化趋势,推荐适宜饮食
2. 家庭健康管理
  • 成员管理
    • 支持添加5-10位家庭成员
    • 儿童/老人专属健康档案
  • 数据看板
    • 全家健康数据可视化对比
    • 疫苗接种提醒(支持设置儿童疫苗时间表)
  • 共享功能
    • 紧急联系人健康警报
    • 用药提醒共享(可设置老人服药提醒)
3. 医疗数据对接
  • 对接方式
    • 支持HL7/FHIR医疗数据标准
    • 三甲医院电子病历系统对接
  • 数据整合
    • 自动同步体检报告(支持PDF/图片解析)
    • 检验指标趋势分析(如:自动绘制半年血脂变化曲线)
  • 隐私保护:采用区块链技术加密敏感医疗数据
4. 运动课程
  • 课程体系
    • 200+专业教学视频(含瑜伽/HIIT/康复训练)
    • 根据BMI推荐课程难度
  • 智能功能
    • AR动作矫正(通过摄像头实时反馈)
    • 运动损伤风险评估
    • 课程效果追踪(记录卡路里消耗/肌肉激活程度)
  • 场景案例:产后修复专属课程+进度跟踪
5. 饮食识别
  • 识别技术
    • CNN图像识别算法(准确率>92%)
    • 支持5000+种常见食物
  • 营养分析
    • 自动计算餐食热量/营养构成
    • 过敏原提示(如海鲜/花生过敏警告)
  • 智能推荐
    • 根据健康目标推荐食谱(减脂/增肌/控糖)
    • 超市购物扫码营养分析
  • 数据整合:与智能冰箱联动记录饮食数据

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐