flutter_for_openharmony口腔护理app实战+周报告实现
本文介绍了一个使用Flutter实现的健康应用周报告页面,主要包含以下功能: 周期信息展示:显示本周报告的时间范围 核心统计指标:刷牙次数、日均刷牙、连续天数和获得积分 刷牙趋势图表:使用fl_chart库实现的柱状图展示一周刷牙数据 卡片式布局:采用网格布局展示统计指标,每个卡片包含图标、数值和标签 页面通过Consumer获取数据,使用Row、Column等基础组件构建界面,实现了数据可视化展

前言
数据可视化是现代健康类应用的重要特性。通过周报告功能,用户可以直观地了解自己一周的口腔护理情况,包括刷牙次数、护理完成度、连续坚持天数等关键指标。这种数据反馈能够有效激励用户保持良好的护理习惯。
本文将详细介绍如何使用 Flutter 和 fl_chart 图表库实现一个功能丰富的周报告页面。
功能需求
周报告页面需要展示以下内容:
- 周期信息:显示当前报告的时间范围
- 核心指标:刷牙次数、日均刷牙、连续天数、获得积分
- 趋势图表:使用柱状图展示一周内每天的刷牙次数
- 完成情况:展示刷牙、漱口、牙线等护理项目的完成进度
- 个性化建议:根据数据给出针对性的改进建议
页面基础结构
周报告页面使用 StatelessWidget 实现,数据通过 Consumer 从 AppProvider 获取:
class WeeklyReportPage extends StatelessWidget {
const WeeklyReportPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('周报告')),
body: Consumer<AppProvider>(
builder: (context, provider, _) {
final weeklyData = provider.getWeeklyBrushData();
final totalBrush = weeklyData.reduce((a, b) => a + b);
final avgBrush = (totalBrush / 7).toStringAsFixed(1);
在构建界面之前,先从 Provider 获取周数据并计算统计值。reduce 方法用于计算数组元素的总和,toStringAsFixed(1) 保留一位小数。
周期信息卡片
页面顶部展示报告的时间范围:
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF26A69A),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('本周报告',
style: TextStyle(color: Colors.white,
fontSize: 20, fontWeight: FontWeight.bold)),
Text(
'${DateFormat('MM/dd').format(DateTime.now().subtract(const Duration(days: 6)))} - ${DateFormat('MM/dd').format(DateTime.now())}',
style: const TextStyle(color: Colors.white70),
),
],
),
const Icon(Icons.analytics, color: Colors.white, size: 40),
],
),
),
使用主题色作为背景,白色文字形成对比。日期范围通过 DateFormat 格式化,显示从6天前到今天的时间段。
统计卡片网格
核心指标使用两行两列的网格布局展示:
const SizedBox(height: 20),
Row(
children: [
Expanded(child: _buildStatCard('刷牙次数', '$totalBrush次', Icons.brush)),
const SizedBox(width: 12),
Expanded(child: _buildStatCard('日均刷牙', '$avgBrush次', Icons.trending_up)),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _buildStatCard('连续天数', '${provider.streakDays}天', Icons.local_fire_department)),
const SizedBox(width: 12),
Expanded(child: _buildStatCard('获得积分', '+${provider.totalPoints ~/ 4}', Icons.stars)),
],
),
使用 Row 和 Expanded 组合实现等宽的两列布局,每个统计项占据一半宽度。
统计卡片组件的实现:
Widget _buildStatCard(String label, String value, IconData icon) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFF26A69A).withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: const Color(0xFF26A69A), size: 20),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(value, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
Text(label, style: TextStyle(color: Colors.grey.shade600, fontSize: 12)),
],
),
],
),
);
}
每个卡片包含图标、数值和标签三个元素。图标使用圆形浅色背景,数值使用大号加粗字体突出显示。
刷牙趋势图表
使用 fl_chart 库绘制柱状图展示一周的刷牙趋势:
const SizedBox(height: 24),
const Text('刷牙趋势',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
Container(
height: 200,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: 5,
barTouchData: BarTouchData(enabled: false),
图表容器设置固定高度200像素,maxY 设为5表示Y轴最大值,barTouchData 禁用触摸交互简化实现。
底部标题配置,显示周一到周日:
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
return Text(days[value.toInt()],
style: const TextStyle(fontSize: 10));
},
),
),
getTitlesWidget 回调函数根据索引返回对应的星期文字,字体设为10像素保持紧凑。
左侧标题配置,显示数值刻度:
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) {
return Text('${value.toInt()}',
style: const TextStyle(fontSize: 10));
},
),
),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
reservedSize 为左侧标题预留30像素宽度,顶部和右侧标题隐藏,边框也隐藏以保持简洁。
柱状图数据生成:
barGroups: List.generate(7, (index) {
return BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: weeklyData[index].toDouble(),
color: const Color(0xFF26A69A),
width: 20,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
),
],
);
}),
),
),
),
使用 List.generate 生成7个柱状图组,每个柱子的高度对应当天的刷牙次数。柱子宽度20像素,顶部圆角4像素。
护理完成情况
展示各项护理的完成进度:
const SizedBox(height: 24),
const Text('护理完成情况',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
_buildCompletionItem('刷牙', totalBrush, 21, const Color(0xFF26A69A)),
_buildCompletionItem('漱口', provider.mouthwashRecords.length, 14, const Color(0xFF42A5F5)),
_buildCompletionItem('牙线', provider.flossRecords.length, 7, const Color(0xFFAB47BC)),
三种护理类型使用不同的颜色区分:刷牙绿色、漱口蓝色、牙线紫色。目标值分别是21次、14次、7次。
完成进度组件的实现:
Widget _buildCompletionItem(String label, int current, int target, Color color) {
final percent = (current / target).clamp(0.0, 1.0);
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
Text('$current/$target', style: TextStyle(color: Colors.grey.shade600)),
],
),
首先计算完成百分比,使用 clamp 确保值在0到1之间。顶部显示标签和完成数量。
进度条的实现:
const SizedBox(height: 8),
LinearProgressIndicator(
value: percent,
backgroundColor: Colors.grey.shade200,
valueColor: AlwaysStoppedAnimation<Color>(color),
minHeight: 8,
borderRadius: BorderRadius.circular(4),
),
],
),
);
}
使用 LinearProgressIndicator 组件显示进度,设置最小高度8像素和圆角4像素,让进度条更加美观。
个性化建议
根据用户的护理数据给出针对性建议:
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.amber.shade50,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.lightbulb, color: Colors.amber.shade700),
const SizedBox(width: 8),
const Text('本周建议',
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 8),
Text(
_getWeeklyAdvice(totalBrush,
provider.mouthwashRecords.length,
provider.flossRecords.length),
style: TextStyle(color: Colors.grey.shade700),
),
],
),
),
],
),
);
},
),
);
}
建议卡片使用琥珀色浅色背景,配合灯泡图标,营造出温馨的提示氛围。
建议生成逻辑:
String _getWeeklyAdvice(int brush, int mouthwash, int floss) {
if (brush >= 21 && mouthwash >= 14 && floss >= 7) {
return '太棒了!本周口腔护理非常到位,继续保持这个好习惯!';
} else if (brush >= 14) {
return '刷牙习惯不错,建议增加漱口水和牙线的使用频率,让口腔护理更全面。';
} else {
return '本周刷牙次数偏少,建议每天至少刷牙2次,每次2-3分钟,保护牙齿健康。';
}
}
根据三项护理的完成情况,返回不同的建议文案。这种个性化的反馈能够帮助用户了解自己的不足并改进。
Provider 数据方法
在 AppProvider 中实现获取周数据的方法:
List<int> getWeeklyBrushData() {
final now = DateTime.now();
final weekData = <int>[];
for (int i = 6; i >= 0; i--) {
final date = now.subtract(Duration(days: i));
final count = _brushRecords.where((record) {
return record.date.year == date.year &&
record.date.month == date.month &&
record.date.day == date.day;
}).length;
weekData.add(count);
}
return weekData;
}
遍历最近7天,统计每天的刷牙记录数量。使用日期比较确保只统计当天的记录。
连续天数的计算:
int get streakDays {
if (_brushRecords.isEmpty) return 0;
int streak = 0;
var checkDate = DateTime.now();
while (true) {
final hasRecord = _brushRecords.any((record) {
return record.date.year == checkDate.year &&
record.date.month == checkDate.month &&
record.date.day == checkDate.day;
});
if (hasRecord) {
streak++;
checkDate = checkDate.subtract(const Duration(days: 1));
} else {
break;
}
}
return streak;
}
从今天开始往前检查,只要当天有刷牙记录就增加连续天数,遇到没有记录的日期就停止。
fl_chart 依赖配置
使用 fl_chart 需要在 pubspec.yaml 中添加依赖:
dependencies:
fl_chart: ^0.65.0
然后在代码中导入:
import 'package:fl_chart/fl_chart.dart';
fl_chart 是一个功能强大的 Flutter 图表库,支持折线图、柱状图、饼图等多种图表类型。
图表交互增强
如果需要添加触摸交互,可以配置 barTouchData:
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
tooltipBgColor: Colors.blueGrey,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
final days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
return BarTooltipItem(
'${days[group.x]}\n${rod.toY.toInt()}次',
const TextStyle(color: Colors.white),
);
},
),
),
配置后用户点击柱子时会显示详细信息的提示框,包含星期和具体次数。
动画效果
fl_chart 支持图表动画,可以通过 swapAnimationDuration 配置:
BarChart(
BarChartData(...),
swapAnimationDuration: const Duration(milliseconds: 300),
swapAnimationCurve: Curves.easeInOut,
)
当数据变化时,图表会以动画形式过渡到新状态,提升用户体验。
数据导出功能思路
周报告可以考虑添加导出功能,让用户保存或分享自己的护理数据:
Future<void> exportReport() async {
final reportData = {
'period': '${startDate} - ${endDate}',
'totalBrush': totalBrush,
'avgBrush': avgBrush,
'streakDays': streakDays,
'dailyData': weeklyData,
};
// 转换为 JSON 或生成 PDF
final jsonStr = jsonEncode(reportData);
// 保存或分享
}
可以将数据导出为 JSON 格式,或者使用 pdf 库生成 PDF 报告。
性能优化建议
对于周报告这种数据密集型页面,可以考虑以下优化:
缓存计算结果:周数据的计算可以缓存起来,只在数据变化时重新计算。
List<int>? _cachedWeeklyData;
DateTime? _cacheDate;
List<int> getWeeklyBrushData() {
final today = DateTime.now();
if (_cachedWeeklyData != null &&
_cacheDate?.day == today.day) {
return _cachedWeeklyData!;
}
// 重新计算
_cachedWeeklyData = _calculateWeeklyData();
_cacheDate = today;
return _cachedWeeklyData!;
}
延迟加载:如果数据量大,可以先显示骨架屏,数据加载完成后再渲染图表。
响应式设计
为了适配不同屏幕尺寸,可以使用 LayoutBuilder 动态调整图表高度:
LayoutBuilder(
builder: (context, constraints) {
final chartHeight = constraints.maxWidth * 0.5;
return Container(
height: chartHeight,
child: BarChart(...),
);
},
)
图表高度设为宽度的一半,在不同设备上保持合适的比例。
总结
本文详细介绍了口腔护理 App 中周报告功能的实现。通过 fl_chart 图表库和合理的数据处理,我们构建了一个信息丰富、视觉美观的数据报告页面。核心技术点包括:
- 使用 fl_chart 绘制柱状图展示趋势数据
- 通过
LinearProgressIndicator展示完成进度 - 根据数据动态生成个性化建议
- 合理的数据计算和缓存策略
数据可视化是提升用户粘性的有效手段,希望本文对你实现类似功能有所启发。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)