Flutter每日心情日记应用开发教程

项目简介

每日心情日记是一款帮助用户记录和分析日常心情变化的Flutter应用。通过简洁直观的界面,用户可以轻松记录每天的心情状态、活动内容、能量水平等信息,并通过统计分析功能了解自己的情绪变化趋势。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心功能

  • 心情记录:支持10种心情类型,包含表情符号和颜色标识
  • 多维度记录:天气、活动、能量等级、压力水平、感恩反思
  • 智能统计:心情分布、能量压力分析、活动统计、周趋势图
  • 日历视图:直观展示每日心情状态
  • 搜索筛选:支持内容和标签搜索
  • 数据管理:备份、恢复、导出功能

技术特点

  • 单文件架构,代码结构清晰
  • Material Design 3设计风格
  • 响应式布局,适配不同屏幕
  • 丰富的交互动画和视觉反馈
  • 完整的CRUD操作支持

架构设计

整体架构

配置类

MoodConfig

WeatherConfig

ActivityConfig

数据模型

MoodEntry

MoodType枚举

WeatherType枚举

ActivityType枚举

MoodDiaryApp

MoodDiaryHomePage

日记页面

统计页面

日历页面

设置页面

添加日记对话框

日记详情对话框

编辑日记对话框

页面结构

应用采用底部导航栏设计,包含四个主要页面:

  1. 日记页面:记录查看和管理
  2. 统计页面:数据分析和可视化
  3. 日历页面:日历视图和历史记录
  4. 设置页面:应用配置和数据管理

数据模型设计

核心数据模型

MoodEntry(心情记录)
class MoodEntry {
  final String id;              // 唯一标识
  final DateTime date;          // 记录日期
  final MoodType mood;          // 心情类型
  final String title;           // 标题
  final String content;         // 内容
  final List<String> tags;      // 标签列表
  final WeatherType weather;    // 天气类型
  final List<ActivityType> activities; // 活动列表
  final int energyLevel;        // 能量等级(1-5)
  final int stressLevel;        // 压力等级(1-5)
  final String gratitude;       // 感恩内容
  final String reflection;      // 反思内容
}
枚举类型定义

心情类型(MoodType)

  • veryHappy - 非常开心 😄
  • happy - 开心 😊
  • neutral - 平静 😐
  • sad - 难过 😢
  • verySad - 非常难过 😭
  • angry - 生气 😠
  • anxious - 焦虑 😰
  • excited - 兴奋 🤩
  • tired - 疲惫 😴
  • grateful - 感恩 🙏

天气类型(WeatherType)

  • sunny - 晴天 ☀️
  • cloudy - 多云 ☁️
  • rainy - 雨天 🌧️
  • snowy - 雪天 ❄️
  • windy - 大风 💨
  • foggy - 雾天 🌫️

活动类型(ActivityType)

  • work - 工作 💼
  • study - 学习 📚
  • exercise - 运动 🏃
  • social - 社交 👥
  • family - 家庭 👨‍👩‍👧‍👦
  • hobby - 爱好 🎨
  • travel - 旅行 ✈️
  • shopping - 购物 🛍️
  • cooking - 烹饪 🍳
  • reading - 阅读 📖
  • music - 音乐 🎵
  • movie - 电影 🎬
  • game - 游戏 🎮
  • rest - 休息 😴

配置类设计

MoodConfig(心情配置)
class MoodConfig {
  final String name;     // 心情名称
  final String emoji;    // 表情符号
  final Color color;     // 主题颜色
}
WeatherConfig(天气配置)
class WeatherConfig {
  final String name;     // 天气名称
  final IconData icon;   // 图标
  final Color color;     // 主题颜色
}
ActivityConfig(活动配置)
class ActivityConfig {
  final String name;     // 活动名称
  final IconData icon;   // 图标
  final Color color;     // 主题颜色
}

核心功能实现

1. 日记页面实现

页面头部设计
Widget _buildDiaryHeader() {
  return Container(
    padding: const EdgeInsets.fromLTRB(16, 48, 16, 16),
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.pink.shade600, Colors.pink.shade400],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
    ),
    child: Column(
      children: [
        // 标题栏
        Row(
          children: [
            const Icon(Icons.favorite, color: Colors.white, size: 32),
            const SizedBox(width: 12),
            const Expanded(
              child: Text(
                '每日心情日记',
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                ),
              ),
            ),
          ],
        ),
        // 搜索框
        TextField(
          controller: _searchController,
          decoration: InputDecoration(
            hintText: '搜索日记内容或标签...',
            prefixIcon: const Icon(Icons.search),
            filled: true,
            fillColor: Colors.white,
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
              borderSide: BorderSide.none,
            ),
          ),
        ),
        // 统计卡片
        Row(
          children: [
            Expanded(child: _buildSummaryCard('总记录', '$totalEntries', '篇', Icons.book)),
            Expanded(child: _buildSummaryCard('今日', '$todayEntry', '篇', Icons.today)),
            Expanded(child: _buildSummaryCard('本周', '$thisWeekEntries', '篇', Icons.date_range)),
          ],
        ),
      ],
    ),
  );
}
日记卡片设计
Widget _buildEntryCard(MoodEntry entry) {
  final moodConfig = _moodConfigs[entry.mood]!;
  final weatherConfig = _weatherConfigs[entry.weather]!;

  return Card(
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () => _showEntryDetails(entry),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 头部:心情表情 + 日期信息 + 天气图标
            Row(
              children: [
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: moodConfig.color.withValues(alpha: 0.2),
                    borderRadius: BorderRadius.circular(30),
                  ),
                  child: Center(
                    child: Text(moodConfig.emoji, style: const TextStyle(fontSize: 28)),
                  ),
                ),
                // 日期和标题信息
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(entry.dateString, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                      Text('${entry.weekdayString} · ${moodConfig.name}'),
                      if (entry.title.isNotEmpty) Text(entry.title),
                    ],
                  ),
                ),
                // 天气图标
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: weatherConfig.color.withValues(alpha: 0.2),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(weatherConfig.icon, color: weatherConfig.color, size: 20),
                ),
              ],
            ),
            // 内容预览
            if (entry.content.isNotEmpty) 
              Text(entry.content, maxLines: 3, overflow: TextOverflow.ellipsis),
            // 能量压力指示器和活动图标
            Row(
              children: [
                _buildLevelIndicator('能量', entry.energyLevel, Colors.green),
                _buildLevelIndicator('压力', entry.stressLevel, Colors.red),
                const Spacer(),
                // 活动图标展示
                ...entry.activities.take(3).map((activity) => _buildActivityIcon(activity)),
              ],
            ),
            // 标签展示
            if (entry.tags.isNotEmpty) _buildTagsRow(entry.tags),
          ],
        ),
      ),
    ),
  );
}

2. 添加日记对话框

心情选择界面
Widget _buildMoodSelection() {
  return Wrap(
    spacing: 8,
    runSpacing: 8,
    children: widget.moodConfigs.entries.map((entry) {
      final isSelected = _selectedMood == entry.key;
      return GestureDetector(
        onTap: () => setState(() => _selectedMood = entry.key),
        child: Container(
          padding: const EdgeInsets.all(8),
          decoration: BoxDecoration(
            color: isSelected 
              ? entry.value.color.withValues(alpha: 0.3)
              : Colors.grey.shade100,
            borderRadius: BorderRadius.circular(12),
            border: isSelected 
              ? Border.all(color: entry.value.color, width: 2) 
              : null,
          ),
          child: Column(
            children: [
              Text(entry.value.emoji, style: const TextStyle(fontSize: 24)),
              Text(entry.value.name, style: TextStyle(fontSize: 10)),
            ],
          ),
        ),
      );
    }).toList(),
  );
}
能量和压力滑块
Widget _buildEnergyStressSliders() {
  return Column(
    children: [
      Text('能量等级:${_energyLevel}'),
      Slider(
        value: _energyLevel.toDouble(),
        min: 1,
        max: 5,
        divisions: 4,
        onChanged: (value) => setState(() => _energyLevel = value.round()),
      ),
      Text('压力等级:${_stressLevel}'),
      Slider(
        value: _stressLevel.toDouble(),
        min: 1,
        max: 5,
        divisions: 4,
        onChanged: (value) => setState(() => _stressLevel = value.round()),
      ),
    ],
  );
}
活动选择界面
Widget _buildActivitySelection() {
  return Wrap(
    spacing: 8,
    runSpacing: 8,
    children: widget.activityConfigs.entries.map((entry) {
      final isSelected = _selectedActivities.contains(entry.key);
      return GestureDetector(
        onTap: () {
          setState(() {
            if (isSelected) {
              _selectedActivities.remove(entry.key);
            } else {
              _selectedActivities.add(entry.key);
            }
          });
        },
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          decoration: BoxDecoration(
            color: isSelected 
              ? entry.value.color.withValues(alpha: 0.3)
              : Colors.grey.shade100,
            borderRadius: BorderRadius.circular(12),
            border: isSelected ? Border.all(color: entry.value.color) : null,
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(entry.value.icon, size: 16, color: entry.value.color),
              const SizedBox(width: 4),
              Text(entry.value.name, style: TextStyle(fontSize: 12)),
            ],
          ),
        ),
      );
    }).toList(),
  );
}

3. 统计分析功能

心情分布统计
Widget _buildMoodStats() {
  final moodStats = <MoodType, int>{};
  for (final entry in _moodEntries) {
    moodStats[entry.mood] = (moodStats[entry.mood] ?? 0) + 1;
  }

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('心情分布', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 16),
          ...moodStats.entries.map((entry) {
            final config = _moodConfigs[entry.key]!;
            final percentage = _moodEntries.isNotEmpty 
              ? (entry.value / _moodEntries.length) * 100 
              : 0.0;

            return Padding(
              padding: const EdgeInsets.only(bottom: 12),
              child: Row(
                children: [
                  Text(config.emoji, style: const TextStyle(fontSize: 20)),
                  const SizedBox(width: 12),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Text(config.name),
                            Text('${entry.value}次 (${percentage.toStringAsFixed(1)}%)'),
                          ],
                        ),
                        const SizedBox(height: 4),
                        LinearProgressIndicator(
                          value: percentage / 100,
                          backgroundColor: Colors.grey.shade200,
                          valueColor: AlwaysStoppedAnimation<Color>(config.color),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            );
          }),
        ],
      ),
    ),
  );
}
能量压力分析
Widget _buildEnergyStressStats() {
  final avgEnergy = _moodEntries.isNotEmpty
    ? _moodEntries.fold(0, (sum, entry) => sum + entry.energyLevel) / _moodEntries.length
    : 0.0;
  final avgStress = _moodEntries.isNotEmpty
    ? _moodEntries.fold(0, (sum, entry) => sum + entry.stressLevel) / _moodEntries.length
    : 0.0;

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('能量与压力', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(child: _buildAvgStatItem('平均能量', avgEnergy, Colors.green)),
              const SizedBox(width: 16),
              Expanded(child: _buildAvgStatItem('平均压力', avgStress, Colors.red)),
            ],
          ),
        ],
      ),
    ),
  );
}
周趋势分析
Widget _buildWeeklyTrend() {
  final now = DateTime.now();
  final weekData = <String, List<MoodEntry>>{};

  // 获取最近7天的数据
  for (int i = 6; i >= 0; i--) {
    final date = now.subtract(Duration(days: i));
    final dateKey = '${date.month}/${date.day}';
    weekData[dateKey] = _moodEntries.where((entry) => _isSameDay(entry.date, date)).toList();
  }

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('本周趋势', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 16),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: weekData.entries.map((entry) {
              final hasEntry = entry.value.isNotEmpty;
              final avgMood = hasEntry
                ? entry.value.fold(0, (sum, e) => sum + _getMoodValue(e.mood)) / entry.value.length
                : 0.0;

              return Column(
                children: [
                  Text(entry.key, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
                  const SizedBox(height: 8),
                  Container(
                    width: 32,
                    height: hasEntry ? (avgMood * 8 + 16) : 16,
                    decoration: BoxDecoration(
                      color: hasEntry ? _getMoodColor(avgMood) : Colors.grey.shade300,
                      borderRadius: BorderRadius.circular(16),
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(hasEntry ? entry.value.length.toString() : '0'),
                ],
              );
            }).toList(),
          ),
        ],
      ),
    ),
  );
}

4. 日历页面实现

日历网格视图
Widget _buildCalendarView() {
  final now = DateTime.now();
  final firstDayOfMonth = DateTime(now.year, now.month, 1);
  final lastDayOfMonth = DateTime(now.year, now.month + 1, 0);
  final daysInMonth = lastDayOfMonth.day;
  final firstWeekday = firstDayOfMonth.weekday;

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          // 月份标题和导航
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text('${now.year}${now.month}月', 
                style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              Row(
                children: [
                  IconButton(icon: const Icon(Icons.chevron_left), onPressed: () {}),
                  IconButton(icon: const Icon(Icons.chevron_right), onPressed: () {}),
                ],
              ),
            ],
          ),
          // 星期标题
          Row(
            children: ['一', '二', '三', '四', '五', '六', '日'].map((day) {
              return Expanded(
                child: Center(
                  child: Text(day, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
                ),
              );
            }).toList(),
          ),
          // 日期网格
          ...List.generate((daysInMonth + firstWeekday - 1) ~/ 7 + 1, (weekIndex) {
            return Row(
              children: List.generate(7, (dayIndex) {
                final dayNumber = weekIndex * 7 + dayIndex - firstWeekday + 2;
                if (dayNumber < 1 || dayNumber > daysInMonth) {
                  return const Expanded(child: SizedBox(height: 40));
                }

                final date = DateTime(now.year, now.month, dayNumber);
                final entries = _moodEntries.where((entry) => _isSameDay(entry.date, date)).toList();
                final hasEntry = entries.isNotEmpty;
                final isToday = _isSameDay(date, now);

                return Expanded(
                  child: Container(
                    height: 40,
                    margin: const EdgeInsets.all(2),
                    decoration: BoxDecoration(
                      color: isToday
                        ? Colors.pink.shade100
                        : (hasEntry ? _moodConfigs[entries.first.mood]!.color.withValues(alpha: 0.3) : null),
                      borderRadius: BorderRadius.circular(8),
                      border: isToday ? Border.all(color: Colors.pink, width: 2) : null,
                    ),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Text(dayNumber.toString(), style: TextStyle(fontSize: 12)),
                          if (hasEntry) Text(_moodConfigs[entries.first.mood]!.emoji, style: const TextStyle(fontSize: 8)),
                        ],
                      ),
                    ),
                  ),
                );
              }),
            );
          }),
        ],
      ),
    ),
  );
}

UI组件设计

1. 等级指示器组件

Widget _buildLevelIndicator(String label, int level, Color color) {
  return Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      Text(label, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
      const SizedBox(width: 4),
      Row(
        children: List.generate(5, (index) {
          return Container(
            width: 8,
            height: 8,
            margin: const EdgeInsets.only(right: 2),
            decoration: BoxDecoration(
              color: index < level ? color : Colors.grey.shade300,
              borderRadius: BorderRadius.circular(4),
            ),
          );
        }),
      ),
    ],
  );
}

2. 统计卡片组件

Widget _buildSummaryCard(String title, String value, String unit, IconData icon) {
  return Container(
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: Colors.white.withValues(alpha: 0.2),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      children: [
        Icon(icon, color: Colors.white, size: 20),
        const SizedBox(height: 4),
        Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
        Text(unit, style: const TextStyle(fontSize: 10, color: Colors.white70)),
        const SizedBox(height: 2),
        Text(title, style: const TextStyle(fontSize: 12, color: Colors.white70)),
      ],
    ),
  );
}

3. 平均统计项组件

Widget _buildAvgStatItem(String title, double value, Color color) {
  return Container(
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      children: [
        Text(value.toStringAsFixed(1), 
          style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color)),
        const SizedBox(height: 4),
        Text(title, style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
        const SizedBox(height: 8),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: List.generate(5, (index) {
            return Container(
              width: 12,
              height: 12,
              margin: const EdgeInsets.only(right: 4),
              decoration: BoxDecoration(
                color: index < value.round() ? color : Colors.grey.shade300,
                borderRadius: BorderRadius.circular(6),
              ),
            );
          }),
        ),
      ],
    ),
  );
}

对话框组件实现

1. 日记详情对话框

class _EntryDetailsDialog extends StatelessWidget {
  final MoodEntry entry;
  final Map<MoodType, MoodConfig> moodConfigs;
  final Map<WeatherType, WeatherConfig> weatherConfigs;
  final Map<ActivityType, ActivityConfig> activityConfigs;
  final Function(MoodEntry) onEdit;
  final VoidCallback onDelete;

  
  Widget build(BuildContext context) {
    final moodConfig = moodConfigs[entry.mood]!;
    final weatherConfig = weatherConfigs[entry.weather]!;

    return AlertDialog(
      title: Row(
        children: [
          Text(moodConfig.emoji, style: const TextStyle(fontSize: 24)),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(entry.title.isNotEmpty ? entry.title : '心情记录'),
                Text(entry.dateString, style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
              ],
            ),
          ),
        ],
      ),
      content: SizedBox(
        width: 400,
        height: 500,
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 心情和天气展示
              _buildMoodWeatherDisplay(moodConfig, weatherConfig),
              // 内容展示
              if (entry.content.isNotEmpty) _buildContentSection(entry.content),
              // 能量压力展示
              _buildEnergyStressDisplay(entry.energyLevel, entry.stressLevel),
              // 活动展示
              if (entry.activities.isNotEmpty) _buildActivitiesDisplay(entry.activities),
              // 感恩展示
              if (entry.gratitude.isNotEmpty) _buildGratitudeDisplay(entry.gratitude),
              // 反思展示
              if (entry.reflection.isNotEmpty) _buildReflectionDisplay(entry.reflection),
              // 标签展示
              if (entry.tags.isNotEmpty) _buildTagsDisplay(entry.tags),
            ],
          ),
        ),
      ),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: const Text('关闭')),
        TextButton(onPressed: () => _showEditDialog(context), child: const Text('编辑')),
        TextButton(
          onPressed: () => _showDeleteConfirmation(context),
          style: TextButton.styleFrom(foregroundColor: Colors.red),
          child: const Text('删除'),
        ),
      ],
    );
  }
}

2. 编辑日记对话框

编辑对话框复用添加对话框的大部分UI组件,但需要预填充现有数据:

class _EditEntryDialog extends StatefulWidget {
  final MoodEntry entry;
  // ... 其他属性

  
  State<_EditEntryDialog> createState() => _EditEntryDialogState();
}

class _EditEntryDialogState extends State<_EditEntryDialog> {
  
  void initState() {
    super.initState();
    // 预填充现有数据
    _titleController = TextEditingController(text: widget.entry.title);
    _contentController = TextEditingController(text: widget.entry.content);
    _gratitudeController = TextEditingController(text: widget.entry.gratitude);
    _reflectionController = TextEditingController(text: widget.entry.reflection);
    
    _selectedMood = widget.entry.mood;
    _selectedWeather = widget.entry.weather;
    _selectedActivities = List.from(widget.entry.activities);
    _energyLevel = widget.entry.energyLevel;
    _stressLevel = widget.entry.stressLevel;
    _tags = List.from(widget.entry.tags);
  }

  void _saveEntry() {
    final editedEntry = widget.entry.copyWith(
      mood: _selectedMood,
      title: _titleController.text.trim(),
      content: _contentController.text.trim(),
      tags: _tags,
      weather: _selectedWeather,
      activities: _selectedActivities,
      energyLevel: _energyLevel,
      stressLevel: _stressLevel,
      gratitude: _gratitudeController.text.trim(),
      reflection: _reflectionController.text.trim(),
    );

    widget.onSave(editedEntry);
    Navigator.pop(context);
  }
}

工具方法实现

1. 日期比较方法

bool _isSameDay(DateTime date1, DateTime date2) {
  return date1.year == date2.year &&
      date1.month == date2.month &&
      date1.day == date2.day;
}

bool _isThisWeek(DateTime date) {
  final now = DateTime.now();
  final startOfWeek = now.subtract(Duration(days: now.weekday - 1));
  final endOfWeek = startOfWeek.add(const Duration(days: 6));
  return date.isAfter(startOfWeek.subtract(const Duration(days: 1))) &&
      date.isBefore(endOfWeek.add(const Duration(days: 1)));
}

2. 心情值转换方法

int _getMoodValue(MoodType mood) {
  switch (mood) {
    case MoodType.verySad: return 1;
    case MoodType.sad: return 2;
    case MoodType.anxious: return 2;
    case MoodType.angry: return 2;
    case MoodType.neutral: return 3;
    case MoodType.tired: return 3;
    case MoodType.happy: return 4;
    case MoodType.grateful: return 4;
    case MoodType.excited: return 5;
    case MoodType.veryHappy: return 5;
  }
}

Color _getMoodColor(double value) {
  if (value <= 2) return Colors.blue;
  if (value <= 3) return Colors.grey;
  if (value <= 4) return Colors.yellow;
  return Colors.orange;
}

3. 搜索和筛选方法

List<MoodEntry> _getFilteredEntries(bool todayOnly) {
  return _moodEntries.where((entry) {
    // 日期过滤
    if (todayOnly && !_isSameDay(entry.date, DateTime.now())) {
      return false;
    }

    // 搜索过滤
    if (_searchQuery.isNotEmpty) {
      final query = _searchQuery.toLowerCase();
      if (!entry.title.toLowerCase().contains(query) &&
          !entry.content.toLowerCase().contains(query) &&
          !entry.tags.any((tag) => tag.toLowerCase().contains(query))) {
        return false;
      }
    }

    // 心情过滤
    if (_filterMood != null && entry.mood != _filterMood) {
      return false;
    }

    return true;
  }).toList()..sort((a, b) => b.date.compareTo(a.date));
}

功能扩展建议

1. 数据持久化

// 使用SharedPreferences或SQLite存储数据
class MoodDataManager {
  static const String _entriesKey = 'mood_entries';
  
  static Future<void> saveEntries(List<MoodEntry> entries) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = entries.map((entry) => entry.toJson()).toList();
    await prefs.setString(_entriesKey, jsonEncode(jsonList));
  }
  
  static Future<List<MoodEntry>> loadEntries() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_entriesKey);
    if (jsonString != null) {
      final jsonList = jsonDecode(jsonString) as List;
      return jsonList.map((json) => MoodEntry.fromJson(json)).toList();
    }
    return [];
  }
}

2. 数据导出功能

class DataExporter {
  static Future<void> exportToCSV(List<MoodEntry> entries) async {
    final csv = const ListToCsvConverter().convert([
      ['日期', '心情', '标题', '内容', '能量', '压力', '天气', '活动', '标签'],
      ...entries.map((entry) => [
        entry.dateString,
        _moodConfigs[entry.mood]!.name,
        entry.title,
        entry.content,
        entry.energyLevel,
        entry.stressLevel,
        _weatherConfigs[entry.weather]!.name,
        entry.activities.map((a) => _activityConfigs[a]!.name).join(';'),
        entry.tags.join(';'),
      ]),
    ]);
    
    // 保存到文件
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/mood_diary_export.csv');
    await file.writeAsString(csv);
  }
}

3. 提醒通知功能

class NotificationManager {
  static Future<void> scheduleDailyReminder(TimeOfDay time) async {
    final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    
    await flutterLocalNotificationsPlugin.zonedSchedule(
      0,
      '记录今天的心情',
      '花一分钟记录今天的心情和感受吧!',
      _nextInstanceOfTime(time),
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'daily_reminder',
          '每日提醒',
          channelDescription: '提醒用户记录每日心情',
        ),
      ),
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
    );
  }
}

4. 心情趋势预测

class MoodAnalyzer {
  static Map<String, dynamic> analyzeMoodTrends(List<MoodEntry> entries) {
    if (entries.length < 7) return {};
    
    // 计算最近7天的平均心情值
    final recentEntries = entries.take(7).toList();
    final avgMood = recentEntries.fold(0, (sum, entry) => sum + _getMoodValue(entry.mood)) / recentEntries.length;
    
    // 计算趋势(上升/下降/稳定)
    final firstHalf = recentEntries.take(3).toList();
    final secondHalf = recentEntries.skip(4).toList();
    
    final firstAvg = firstHalf.fold(0, (sum, entry) => sum + _getMoodValue(entry.mood)) / firstHalf.length;
    final secondAvg = secondHalf.fold(0, (sum, entry) => sum + _getMoodValue(entry.mood)) / secondHalf.length;
    
    String trend = '稳定';
    if (secondAvg > firstAvg + 0.5) trend = '上升';
    if (secondAvg < firstAvg - 0.5) trend = '下降';
    
    return {
      'averageMood': avgMood,
      'trend': trend,
      'suggestion': _getMoodSuggestion(avgMood, trend),
    };
  }
  
  static String _getMoodSuggestion(double avgMood, String trend) {
    if (avgMood >= 4) return '保持积极的心态,继续加油!';
    if (avgMood >= 3) return '心情还不错,可以尝试一些新的活动。';
    if (trend == '上升') return '心情在好转,坚持下去!';
    return '建议多做一些让自己开心的事情。';
  }
}

5. 社交分享功能

class ShareManager {
  static Future<void> shareMoodSummary(MoodEntry entry) async {
    final moodConfig = _moodConfigs[entry.mood]!;
    final text = '''
今天的心情:${moodConfig.emoji} ${moodConfig.name}
${entry.title.isNotEmpty ? entry.title : ''}
${entry.content.isNotEmpty ? entry.content : ''}
#每日心情日记 #心情记录
    ''';
    
    await Share.share(text);
  }
  
  static Future<void> shareWeeklySummary(List<MoodEntry> weekEntries) async {
    final moodCounts = <MoodType, int>{};
    for (final entry in weekEntries) {
      moodCounts[entry.mood] = (moodCounts[entry.mood] ?? 0) + 1;
    }
    
    final mostFrequentMood = moodCounts.entries
        .reduce((a, b) => a.value > b.value ? a : b);
    
    final text = '''
本周心情总结:
记录了 ${weekEntries.length} 天
最常见的心情:${_moodConfigs[mostFrequentMood.key]!.emoji} ${_moodConfigs[mostFrequentMood.key]!.name}
#每日心情日记 #周总结
    ''';
    
    await Share.share(text);
  }
}

性能优化策略

1. 列表优化

// 使用ListView.builder进行懒加载
Widget _buildEntriesList(bool todayOnly) {
  final filteredEntries = _getFilteredEntries(todayOnly);
  
  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: filteredEntries.length,
    itemBuilder: (context, index) {
      final entry = filteredEntries[index];
      return _buildEntryCard(entry);
    },
  );
}

// 使用AutomaticKeepAliveClientMixin保持页面状态
class _MoodDiaryHomePageState extends State<MoodDiaryHomePage>
    with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
  
  
  bool get wantKeepAlive => true;
  
  
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return Scaffold(/* ... */);
  }
}

2. 图片和资源优化

// 使用缓存的网络图片
Widget _buildMoodImage(String imageUrl) {
  return CachedNetworkImage(
    imageUrl: imageUrl,
    placeholder: (context, url) => const CircularProgressIndicator(),
    errorWidget: (context, url, error) => const Icon(Icons.error),
    memCacheWidth: 200, // 限制内存中的图片大小
    memCacheHeight: 200,
  );
}

// 预加载关键资源
void _preloadAssets() {
  for (final config in _moodConfigs.values) {
    precacheImage(AssetImage('assets/moods/${config.name}.png'), context);
  }
}

3. 状态管理优化

// 使用Provider进行状态管理
class MoodProvider extends ChangeNotifier {
  List<MoodEntry> _entries = [];
  String _searchQuery = '';
  MoodType? _filterMood;
  
  List<MoodEntry> get entries => _entries;
  List<MoodEntry> get filteredEntries => _getFilteredEntries();
  
  void addEntry(MoodEntry entry) {
    _entries.insert(0, entry);
    notifyListeners();
  }
  
  void updateEntry(MoodEntry entry) {
    final index = _entries.indexWhere((e) => e.id == entry.id);
    if (index != -1) {
      _entries[index] = entry;
      notifyListeners();
    }
  }
  
  void deleteEntry(String id) {
    _entries.removeWhere((e) => e.id == id);
    notifyListeners();
  }
  
  void setSearchQuery(String query) {
    _searchQuery = query;
    notifyListeners();
  }
  
  List<MoodEntry> _getFilteredEntries() {
    return _entries.where((entry) {
      if (_searchQuery.isNotEmpty) {
        final query = _searchQuery.toLowerCase();
        if (!entry.title.toLowerCase().contains(query) &&
            !entry.content.toLowerCase().contains(query) &&
            !entry.tags.any((tag) => tag.toLowerCase().contains(query))) {
          return false;
        }
      }
      
      if (_filterMood != null && entry.mood != _filterMood) {
        return false;
      }
      
      return true;
    }).toList();
  }
}

测试指南

1. 单元测试

// test/mood_entry_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mood_diary/models/mood_entry.dart';

void main() {
  group('MoodEntry', () {
    test('should create mood entry with required fields', () {
      final entry = MoodEntry(
        id: '1',
        date: DateTime.now(),
        mood: MoodType.happy,
      );
      
      expect(entry.id, '1');
      expect(entry.mood, MoodType.happy);
      expect(entry.title, '');
      expect(entry.content, '');
    });
    
    test('should format date string correctly', () {
      final date = DateTime(2024, 1, 15);
      final entry = MoodEntry(
        id: '1',
        date: date,
        mood: MoodType.happy,
      );
      
      expect(entry.dateString, '2024年1月15日');
    });
    
    test('should copy with new values', () {
      final original = MoodEntry(
        id: '1',
        date: DateTime.now(),
        mood: MoodType.happy,
        title: 'Original',
      );
      
      final copied = original.copyWith(title: 'Updated');
      
      expect(copied.title, 'Updated');
      expect(copied.id, original.id);
      expect(copied.mood, original.mood);
    });
  });
}

2. Widget测试

// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mood_diary/main.dart';

void main() {
  group('MoodDiaryApp', () {
    testWidgets('should display navigation bar with 4 tabs', (WidgetTester tester) async {
      await tester.pumpWidget(const MoodDiaryApp());
      
      expect(find.byType(NavigationBar), findsOneWidget);
      expect(find.text('日记'), findsOneWidget);
      expect(find.text('统计'), findsOneWidget);
      expect(find.text('日历'), findsOneWidget);
      expect(find.text('设置'), findsOneWidget);
    });
    
    testWidgets('should show add button on diary page', (WidgetTester tester) async {
      await tester.pumpWidget(const MoodDiaryApp());
      
      expect(find.byType(FloatingActionButton), findsOneWidget);
    });
    
    testWidgets('should open add entry dialog when FAB is tapped', (WidgetTester tester) async {
      await tester.pumpWidget(const MoodDiaryApp());
      
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pumpAndSettle();
      
      expect(find.text('记录今天的心情'), findsOneWidget);
    });
  });
}

3. 集成测试

// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:mood_diary/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('Mood Diary App Integration Tests', () {
    testWidgets('complete mood entry flow', (WidgetTester tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // 点击添加按钮
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pumpAndSettle();
      
      // 选择心情
      await tester.tap(find.text('😊'));
      await tester.pumpAndSettle();
      
      // 输入标题
      await tester.enterText(find.byType(TextFormField).first, '测试标题');
      
      // 输入内容
      await tester.enterText(find.byType(TextFormField).at(1), '测试内容');
      
      // 保存
      await tester.tap(find.text('保存'));
      await tester.pumpAndSettle();
      
      // 验证条目已添加
      expect(find.text('测试标题'), findsOneWidget);
    });
  });
}

部署指南

1. Android部署

# android/app/build.gradle
android {
    compileSdkVersion 34
    
    defaultConfig {
        applicationId "com.example.mood_diary"
        minSdkVersion 21
        targetSdkVersion 34
        versionCode 1
        versionName "1.0.0"
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

2. iOS部署

<!-- ios/Runner/Info.plist -->
<key>CFBundleDisplayName</key>
<string>每日心情日记</string>
<key>CFBundleIdentifier</key>
<string>com.example.moodDiary</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>NSCameraUsageDescription</key>
<string>用于拍摄心情照片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>用于选择心情照片</string>

3. 构建命令

# 构建Android APK
flutter build apk --release

# 构建Android App Bundle
flutter build appbundle --release

# 构建iOS
flutter build ios --release

# 构建Web
flutter build web --release

项目总结

每日心情日记应用通过Flutter框架实现了一个功能完整、界面美观的心情记录工具。应用具有以下特点:

技术亮点

  1. 单文件架构:所有代码集中在一个文件中,便于理解和维护
  2. Material Design 3:采用最新的设计规范,界面现代化
  3. 响应式设计:适配不同屏幕尺寸和方向
  4. 丰富的交互:手势操作、动画效果、视觉反馈
  5. 数据可视化:统计图表、趋势分析、进度指示器

功能完整性

  • ✅ 心情记录和管理
  • ✅ 多维度数据收集
  • ✅ 统计分析功能
  • ✅ 日历视图展示
  • ✅ 搜索和筛选
  • ✅ 数据导入导出
  • ✅ 设置和配置

用户体验

  1. 直观的界面:清晰的导航结构和信息层次
  2. 便捷的操作:一键添加、快速编辑、批量管理
  3. 个性化定制:多种心情类型、活动分类、标签系统
  4. 数据洞察:趋势分析、统计报告、个人总结

扩展性

应用架构支持多种扩展方向:

  • 云端同步和备份
  • 社交分享功能
  • AI智能分析
  • 多媒体支持
  • 团队协作功能

通过本教程的学习,开发者可以掌握Flutter应用开发的核心技能,包括状态管理、UI设计、数据处理、用户交互等方面的知识。同时,这个项目也为进一步的功能扩展和商业化应用提供了良好的基础。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐