flutter_for_openharmony口腔护理app实战+饮食记录实现

前言

饮食习惯与口腔健康密切相关。高糖食物、碳酸饮料等会增加龋齿风险,而牛奶、蔬菜等则有益于牙齿健康。在口腔护理应用中,记录饮食并标注其对牙齿的影响,可以帮助用户更好地了解自己的饮食习惯,从而做出更健康的选择。

本文将介绍如何在 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

相关推荐
独自破碎E2 小时前
【滑动窗口+字符计数数组】LCR_014_字符串的排列
android·java·开发语言
2601_949480062 小时前
【无标题】
开发语言·前端·javascript
stevenzqzq2 小时前
compose 中 align和Arrangement的区别
android·compose
css趣多多2 小时前
Vue过滤器
前端·javascript·vue.js
VincentWei952 小时前
Compose:MutableState 和 mutableStateOf
android
jian110582 小时前
Android studio 调试flutter 运行自己的苹果手机上
flutter·智能手机·android studio
向哆哆2 小时前
高校四六级报名管理系统的考试信息模块实现:Flutter × OpenHarmony 跨端开发实践
flutter·开源·鸿蒙·openharmony·开源鸿蒙
jian110582 小时前
Android studio配置flutter,mac Android studio 发现苹果手机设备
android·flutter·android studio
●VON2 小时前
React Native for OpenHarmony:项目目录结构与跨平台构建流程详解
javascript·学习·react native·react.js·架构·跨平台·von