
前言
饮食习惯与口腔健康密切相关。高糖食物、碳酸饮料等会增加龋齿风险,而牛奶、蔬菜等则有益于牙齿健康。在口腔护理应用中,记录饮食并标注其对牙齿的影响,可以帮助用户更好地了解自己的饮食习惯,从而做出更健康的选择。
本文将介绍如何在 Flutter 中实现一个带有分类标签的饮食记录功能。
功能设计
饮食记录页面需要实现以下功能:
- 记录列表:展示所有饮食记录
- 分类标签:区分有益、中性、有害三种类型
- 颜色编码:不同类型使用不同颜色标识
- 添加记录:支持输入食物名称和选择分类
页面基础结构
饮食记录页面使用 StatelessWidget 实现:
dart
class DietRecordPage extends StatelessWidget {
const DietRecordPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('饮食记录')),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddDialog(context),
backgroundColor: const Color(0xFF26A69A),
child: const Icon(Icons.add),
),
页面结构与其他记录页面保持一致,包含标题栏和浮动操作按钮。
记录列表构建
使用 Consumer 监听数据变化:
dart
body: Consumer<AppProvider>(
builder: (context, provider, _) {
if (provider.dietRecords.isEmpty) {
return const Center(child: Text('暂无饮食记录'));
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: provider.dietRecords.length,
itemBuilder: (context, index) {
final record = provider.dietRecords[index];
标准的空状态检查和列表构建模式。
分类颜色映射
根据食物分类确定颜色和标签:
dart
Color categoryColor;
String categoryLabel;
IconData categoryIcon;
switch (record.category) {
case 'good':
categoryColor = Colors.green;
categoryLabel = '有益';
categoryIcon = Icons.thumb_up;
break;
case 'bad':
categoryColor = Colors.red;
categoryLabel = '有害';
categoryIcon = Icons.thumb_down;
break;
default:
categoryColor = Colors.grey;
categoryLabel = '中性';
categoryIcon = Icons.remove;
}
三种分类使用直观的颜色:绿色表示有益,红色表示有害,灰色表示中性。图标也与分类含义对应。
记录卡片设计
饮食记录卡片带有分类颜色边框:
dart
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: categoryColor.withOpacity(0.3)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: categoryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(categoryIcon, color: categoryColor),
),
卡片边框使用分类颜色的浅色版本,图标容器也使用对应颜色,形成统一的视觉效果。
卡片中间区域展示食物名称和分类标签:
dart
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(record.foodName,
style: const TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: categoryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(categoryLabel,
style: TextStyle(color: categoryColor, fontSize: 11)),
),
分类标签使用小型的彩色标签形式,紧凑且醒目。
备注信息的条件渲染:
dart
if (record.note != null) ...[
const SizedBox(width: 8),
Text(record.note!,
style: TextStyle(color: Colors.grey.shade600,
fontSize: 12)),
],
],
),
],
),
),
使用展开运算符 ...[] 实现条件渲染多个组件,只有存在备注时才显示。
卡片右侧显示时间:
dart
Text(
DateFormat('MM-dd HH:mm').format(record.dateTime),
style: TextStyle(color: Colors.grey.shade500, fontSize: 12),
),
],
),
);
},
);
},
),
);
}
时间格式与其他记录页面保持一致。
添加记录对话框
添加饮食记录需要输入食物名称和选择分类:
dart
void _showAddDialog(BuildContext context) {
final controller = TextEditingController();
String category = 'good';
showDialog(
context: context,
builder: (ctx) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: const Text('添加饮食记录'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: controller,
decoration: const InputDecoration(labelText: '食物名称'),
),
食物名称使用文本输入框,允许用户自由输入。
分类选择下拉框:
dart
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: category,
decoration: const InputDecoration(labelText: '对牙齿的影响'),
items: const [
DropdownMenuItem(value: 'good', child: Text('有益(如牛奶、蔬菜)')),
DropdownMenuItem(value: 'neutral', child: Text('中性')),
DropdownMenuItem(value: 'bad', child: Text('有害(如糖果、碳酸饮料)')),
],
onChanged: (v) => setState(() => category = v!),
),
],
),
下拉选项中添加了示例说明,帮助用户理解每个分类的含义。
对话框操作按钮:
dart
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('取消')
),
ElevatedButton(
onPressed: () {
if (controller.text.isEmpty) return;
final record = DietRecord(
dateTime: DateTime.now(),
foodName: controller.text,
category: category,
);
context.read<AppProvider>().addDietRecord(record);
Navigator.pop(ctx);
},
child: const Text('保存'),
),
],
),
),
);
}
保存前检查食物名称是否为空,避免创建无效记录。
数据模型定义
饮食记录的数据模型:
dart
class DietRecord {
final String id;
final DateTime dateTime;
final String foodName;
final String category;
final String? note;
DietRecord({
String? id,
required this.dateTime,
required this.foodName,
required this.category,
this.note,
}) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString();
}
模型包含时间、食物名称、分类和可选备注四个字段。
Provider 数据管理
在 AppProvider 中管理饮食记录:
dart
List<DietRecord> _dietRecords = [];
List<DietRecord> get dietRecords => _dietRecords;
void addDietRecord(DietRecord record) {
_dietRecords.insert(0, record);
notifyListeners();
}
与其他记录类型保持一致的数据管理模式。
测试数据生成
生成测试数据:
dart
void initTestData() {
_dietRecords = [
DietRecord(
dateTime: DateTime.now().subtract(const Duration(hours: 1)),
foodName: '牛奶',
category: 'good',
),
DietRecord(
dateTime: DateTime.now().subtract(const Duration(hours: 3)),
foodName: '可乐',
category: 'bad',
note: '含糖量高',
),
DietRecord(
dateTime: DateTime.now().subtract(const Duration(hours: 5)),
foodName: '米饭',
category: 'neutral',
),
];
}
测试数据包含三种分类的记录,便于验证不同颜色的显示效果。
分类统计功能
统计各分类的记录数量:
dart
Map<String, int> getDietStats() {
int good = 0, neutral = 0, bad = 0;
for (var record in _dietRecords) {
switch (record.category) {
case 'good':
good++;
break;
case 'neutral':
neutral++;
break;
case 'bad':
bad++;
break;
}
}
return {'good': good, 'neutral': neutral, 'bad': bad};
}
统计数据可以用于展示饮食习惯的整体情况。
统计卡片展示
在列表上方添加统计信息:
dart
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatColumn('有益', stats['good']!, Colors.green),
_buildStatColumn('中性', stats['neutral']!, Colors.grey),
_buildStatColumn('有害', stats['bad']!, Colors.red),
],
),
)
Widget _buildStatColumn(String label, int count, Color color) {
return Column(
children: [
Text('$count',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color)),
Text(label, style: TextStyle(color: Colors.grey.shade600)),
],
);
}
三列统计数据使用对应的颜色,让用户一目了然地看到饮食结构。
饮食建议功能
根据统计数据给出建议:
dart
String getDietAdvice() {
final stats = getDietStats();
final total = stats['good']! + stats['neutral']! + stats['bad']!;
if (total == 0) return '开始记录你的饮食吧!';
final badRatio = stats['bad']! / total;
if (badRatio > 0.3) {
return '有害食物占比较高,建议减少糖分和碳酸饮料的摄入';
} else if (badRatio > 0.1) {
return '饮食结构还不错,继续保持健康饮食习惯';
} else {
return '太棒了!你的饮食习惯非常健康';
}
}
根据有害食物的占比给出个性化建议。
常见食物快捷选择
可以添加常见食物的快捷按钮:
dart
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildQuickFoodChip('牛奶', 'good'),
_buildQuickFoodChip('蔬菜', 'good'),
_buildQuickFoodChip('水果', 'good'),
_buildQuickFoodChip('米饭', 'neutral'),
_buildQuickFoodChip('面包', 'neutral'),
_buildQuickFoodChip('糖果', 'bad'),
_buildQuickFoodChip('可乐', 'bad'),
],
)
Widget _buildQuickFoodChip(String name, String category) {
Color color;
switch (category) {
case 'good':
color = Colors.green;
break;
case 'bad':
color = Colors.red;
break;
default:
color = Colors.grey;
}
return ActionChip(
label: Text(name),
backgroundColor: color.withOpacity(0.1),
onPressed: () {
final record = DietRecord(
dateTime: DateTime.now(),
foodName: name,
category: category,
);
provider.addDietRecord(record);
},
);
}
快捷选择让用户可以一键记录常见食物,提高使用效率。
按日期分组展示
可以将饮食记录按日期分组:
dart
Map<String, List<DietRecord>> groupByDate() {
final grouped = <String, List<DietRecord>>{};
for (var record in _dietRecords) {
final dateKey = DateFormat('yyyy-MM-dd').format(record.dateTime);
grouped.putIfAbsent(dateKey, () => []).add(record);
}
return grouped;
}
按日期分组可以让用户更清晰地看到每天的饮食情况。
食物搜索功能
添加搜索功能帮助用户快速找到记录:
dart
List<DietRecord> searchRecords(String keyword) {
if (keyword.isEmpty) return _dietRecords;
return _dietRecords.where((r) =>
r.foodName.contains(keyword)
).toList();
}
搜索功能在记录较多时非常有用。
筛选功能
按分类筛选记录:
dart
String _selectedCategory = 'all';
List<DietRecord> get filteredRecords {
if (_selectedCategory == 'all') return _dietRecords;
return _dietRecords.where((r) => r.category == _selectedCategory).toList();
}
在页面顶部添加筛选按钮,用户可以只查看某一类食物。
筛选按钮组
实现筛选按钮组:
dart
Row(
children: [
_buildFilterChip('全部', 'all'),
_buildFilterChip('有益', 'good'),
_buildFilterChip('中性', 'neutral'),
_buildFilterChip('有害', 'bad'),
],
)
Widget _buildFilterChip(String label, String value) {
final isSelected = _selectedCategory == value;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
label: Text(label),
selected: isSelected,
onSelected: (selected) {
setState(() => _selectedCategory = value);
},
),
);
}
FilterChip 组件提供了选中状态的视觉反馈。
空状态优化
为空状态添加引导:
dart
if (provider.dietRecords.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.restaurant, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text('暂无饮食记录', style: TextStyle(color: Colors.grey.shade500)),
const SizedBox(height: 8),
Text('记录饮食可以帮助你了解口腔健康风险',
style: TextStyle(color: Colors.grey.shade400, fontSize: 12)),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () => _showAddDialog(context),
icon: const Icon(Icons.add),
label: const Text('添加第一条记录'),
),
],
),
);
}
空状态页面添加健康提示和添加按钮。
总结
本文详细介绍了口腔护理 App 中饮食记录功能的实现。通过颜色编码和分类标签,我们构建了一个直观易用的饮食记录管理页面。核心技术点包括:
- 使用
switch语句映射分类到颜色和图标 - 通过边框颜色强化分类视觉效果
- 使用条件渲染处理可选的备注信息
- 结合文本输入和下拉选择的表单设计
饮食记录功能帮助用户了解饮食习惯对口腔健康的影响,是口腔护理应用的重要组成部分。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net