鸿蒙Flutter实战:构建智能健康管理应用
本文介绍了基于鸿蒙Flutter开发的智能健康管理应用。该应用通过华为穿戴设备实时采集15+项健康指标,提供多终端数据同步与可视化展示,支持手机、平板和智慧屏等多设备协同。核心技术架构采用Riverpod状态管理、Retrofit网络请求和ObjectBox本地存储,实现健康数据加密存储与智能分析。核心功能模块包括:1)健康仪表板实时展示运动、睡眠等数据;2)基于BMI的运动课程推荐;3)智能饮食
·
鸿蒙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 调试技巧
- 检查健康数据权限
# 查看健康数据权限状态
adb shell pm list permissions | grep health
- 模拟健康数据
// 开发模式下使用模拟数据
void main() {
if (kDebugMode) {
HealthService.useMockData = true;
}
runApp(const ProviderScope(child: MyApp()));
}
- 穿戴设备调试
# 查看连接的穿戴设备
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的发布向导完成
六、项目总结
✅ 核心技术点详解:
-
鸿蒙健康服务深度集成
- 通过HarmonyOS Health Kit SDK实现穿戴设备数据接入
- 支持心率、血氧、睡眠等12类健康数据的标准化采集
- 示例:调用
HealthKitManager.getLatestHeartRate()获取最新心率数据 - 权限管理:需申请
ohos.permission.health系列权限
-
分布式多设备数据同步
- 基于HarmonyOS分布式数据管理能力
- 实现手机、平板、智慧屏等多终端健康数据实时同步
- 采用CRDT冲突解决算法保证数据一致性
- 典型场景:运动时手表记录数据,回家后可在平板上查看
-
动态数据可视化引擎
- 支持折线图/柱状图/雷达图等多种健康数据展示形式
- 内置7天/30天/自定义时段的数据趋势分析
- 使用Flutter的CustomPaint实现高性能渲染
- 示例:睡眠质量图表展示深睡/浅睡/REM周期分布
-
Riverpod状态管理方案
- 采用StateNotifierProvider管理复杂健康数据流
- 实现数据变更的自动监听和UI响应
- 支持异步数据加载状态管理(loading/error/success)
- 典型应用:全局健康档案的状态管理
-
服务卡片深度集成
- 提供3种尺寸(2x2/2x4/4x4)的桌面卡片
- 支持实时显示步数、心率等关键健康指标
- 卡片点击可快速跳转至详情页
- 开发要点:使用
FormExtensionAbility实现卡片服务
📈 性能优化建议:
-
数据缓存:使用Isar数据库缓存历史数据
- 实现本地数据持久化存储,减少网络请求次数
- 示例:将用户最近30天的交易记录缓存在本地
- 优势:离线可用,提升二次加载速度
- 实现步骤:
final isar = await Isar.open([TransactionSchema]); await isar.writeTxn(() async { await isar.transactions.putAll(transactions); });
-
按需同步:定时同步而不是实时同步
- 设置合理的同步频率(如每15分钟)
- 使用后台任务定期同步
- 场景:对实时性要求不高的数据(如历史报表)
- 实现方案:
- Android使用WorkManager
- iOS使用Background Fetch
- Flutter可使用
flutter_workmanager插件
-
图片资源优化:使用SVG格式的图表
- 相比PNG/JPG,SVG具有:
- 更小的文件体积
- 无损缩放
- 更好的显示效果
- 适用场景:
- 统计图表
- 图标资源
- 需要多分辨率适配的图片
- 实现示例:
SvgPicture.asset( 'assets/charts/trend.svg', width: 200, height: 200, )
- 相比PNG/JPG,SVG具有:
-
内存管理:及时清理历史数据
- 定期清理策略:
- 保留最近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+种常见食物
- 营养分析:
- 自动计算餐食热量/营养构成
- 过敏原提示(如海鲜/花生过敏警告)
- 智能推荐:
- 根据健康目标推荐食谱(减脂/增肌/控糖)
- 超市购物扫码营养分析
- 数据整合:与智能冰箱联动记录饮食数据
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐


所有评论(0)