Flutter待办事项优先级排序开发教程

项目简介

待办事项优先级排序是一个基于Flutter开发的任务管理应用,专注于通过智能优先级算法帮助用户高效管理日常任务。应用支持四级优先级设置,结合截止日期自动计算任务紧急程度,确保重要任务不被遗漏。
运行效果图
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

核心功能

  • 智能优先级排序:基于优先级和截止日期的综合排序算法
  • 四级优先级管理:紧急、高、中、低四个优先级等级
  • 截止日期提醒:自动识别逾期和即将到期的任务
  • 任务状态管理:支持任务完成状态切换和编辑
  • 统计分析:提供完成率和优先级分布统计
  • 分类筛选:按优先级快速筛选和查看任务

技术特点

  • 单文件架构,代码结构清晰
  • Material Design 3设计风格
  • 智能排序算法,自动计算任务优先级分数
  • 响应式布局适配不同屏幕尺寸

架构设计

整体架构

TodoPriorityApp

TodoHomePage

待办列表页面

优先级页面

统计页面

任务卡片

添加任务对话框

优先级筛选器

分类任务列表

总体统计

优先级分布

完成率图表

TodoItem数据模型

优先级分数算法

核心组件

组件 功能 说明
TodoPriorityApp 应用入口 配置主题和路由
TodoHomePage 主页面 管理页面状态和导航
TodoItem 任务数据模型 封装任务信息和优先级算法
PriorityConfig 优先级配置 定义优先级名称、颜色和权重

数据模型设计

枚举类型

Priority - 优先级枚举
enum Priority {
  urgent,    // 紧急
  high,      // 高
  medium,    // 中
  low,       // 低
}

配置类

PriorityConfig - 优先级配置
class PriorityConfig {
  static const Map<Priority, String> names = {
    Priority.urgent: '紧急',
    Priority.high: '高',
    Priority.medium: '中',
    Priority.low: '低',
  };

  static const Map<Priority, Color> colors = {
    Priority.urgent: Colors.red,
    Priority.high: Colors.orange,
    Priority.medium: Colors.blue,
    Priority.low: Colors.green,
  };

  static const Map<Priority, int> weights = {
    Priority.urgent: 4,
    Priority.high: 3,
    Priority.medium: 2,
    Priority.low: 1,
  };
}

配置说明:

  • names: 优先级的中文显示名称
  • colors: 每个优先级对应的颜色标识
  • weights: 优先级权重,用于排序算法计算

核心数据类

TodoItem - 待办事项模型
class TodoItem {
  final String id;              // 唯一标识
  final String title;           // 任务标题
  final String description;     // 任务描述
  final Priority priority;      // 优先级
  final DateTime createdAt;     // 创建时间
  final DateTime? dueDate;      // 截止日期
  final bool isCompleted;       // 是否完成

  // 计算优先级分数(用于排序)
  int get priorityScore {
    int score = (PriorityConfig.weights[priority] ?? 1) * 100;
    
    // 根据截止日期调整分数
    if (dueDate != null) {
      final now = DateTime.now();
      final daysLeft = dueDate!.difference(now).inDays;
      
      if (daysLeft < 0) {
        score += 200; // 已过期,最高优先级
      } else if (daysLeft == 0) {
        score += 150; // 今天到期
      } else if (daysLeft == 1) {
        score += 100; // 明天到期
      } else if (daysLeft <= 3) {
        score += 50; // 3天内到期
      }
    }
    
    return score;
  }
}

字段说明:

  • id: 任务的唯一标识符
  • title: 任务标题,必填字段
  • description: 任务详细描述,可选
  • priority: 任务优先级等级
  • createdAt: 任务创建时间
  • dueDate: 任务截止日期,可选
  • isCompleted: 任务完成状态

核心功能实现

1. 智能优先级排序算法

优先级分数计算
int get priorityScore {
  // 基础分数 = 优先级权重 × 100
  int score = (PriorityConfig.weights[priority] ?? 1) * 100;
  
  // 截止日期加权
  if (dueDate != null) {
    final now = DateTime.now();
    final daysLeft = dueDate!.difference(now).inDays;
    
    if (daysLeft < 0) {
      score += 200; // 已过期任务优先级最高
    } else if (daysLeft == 0) {
      score += 150; // 今天到期
    } else if (daysLeft == 1) {
      score += 100; // 明天到期
    } else if (daysLeft <= 3) {
      score += 50;  // 3天内到期
    }
  }
  
  return score;
}
排序逻辑实现
List<TodoItem> _getSortedTodos() {
  final todos = List<TodoItem>.from(_todos);
  todos.sort((a, b) {
    // 已完成的任务排在后面
    if (a.isCompleted != b.isCompleted) {
      return a.isCompleted ? 1 : -1;
    }
    
    // 按优先级分数排序(分数高的在前)
    return b.priorityScore.compareTo(a.priorityScore);
  });
  return todos;
}

算法特点:

  • 综合考虑优先级和时间因素
  • 已过期任务自动提升到最高优先级
  • 即将到期的任务获得额外权重
  • 已完成任务自动排到列表末尾

2. 任务管理功能

添加任务对话框
void _showTodoDialog({TodoItem? todo}) {
  final isEdit = todo != null;
  final titleController = TextEditingController(text: todo?.title ?? '');
  final descController = TextEditingController(text: todo?.description ?? '');
  Priority selectedPriority = todo?.priority ?? Priority.medium;
  DateTime? selectedDueDate = todo?.dueDate;

  showDialog(
    context: context,
    builder: (context) => StatefulBuilder(
      builder: (context, setDialogState) => AlertDialog(
        title: Text(isEdit ? '编辑任务' : '添加任务'),
        content: SingleChildScrollView(
          child: Column(
            children: [
              // 任务标题输入
              TextField(
                controller: titleController,
                decoration: const InputDecoration(
                  labelText: '任务标题',
                  border: OutlineInputBorder(),
                ),
              ),
              
              // 任务描述输入
              TextField(
                controller: descController,
                decoration: const InputDecoration(
                  labelText: '任务描述(可选)',
                  border: OutlineInputBorder(),
                ),
                maxLines: 3,
              ),
              
              // 优先级选择
              DropdownButtonFormField<Priority>(
                value: selectedPriority,
                items: Priority.values.map((priority) {
                  return DropdownMenuItem(
                    value: priority,
                    child: Row(
                      children: [
                        Container(
                          width: 12,
                          height: 12,
                          decoration: BoxDecoration(
                            color: PriorityConfig.colors[priority],
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 8),
                        Text(PriorityConfig.names[priority] ?? ''),
                      ],
                    ),
                  );
                }).toList(),
                onChanged: (value) {
                  setDialogState(() {
                    selectedPriority = value!;
                  });
                },
              ),
              
              // 截止日期选择
              ListTile(
                title: const Text('截止日期'),
                subtitle: Text(selectedDueDate != null 
                    ? _formatDate(selectedDueDate!) 
                    : '未设置'),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    IconButton(
                      onPressed: () async {
                        final date = await showDatePicker(
                          context: context,
                          initialDate: selectedDueDate ?? DateTime.now(),
                          firstDate: DateTime.now(),
                          lastDate: DateTime.now().add(const Duration(days: 365)),
                        );
                        if (date != null) {
                          setDialogState(() {
                            selectedDueDate = date;
                          });
                        }
                      },
                      icon: const Icon(Icons.calendar_today),
                    ),
                    if (selectedDueDate != null)
                      IconButton(
                        onPressed: () {
                          setDialogState(() {
                            selectedDueDate = null;
                          });
                        },
                        icon: const Icon(Icons.clear),
                      ),
                  ],
                ),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          ElevatedButton(
            onPressed: () => _saveTodo(),
            child: Text(isEdit ? '更新' : '添加'),
          ),
        ],
      ),
    ),
  );
}
任务状态切换
Widget _buildTodoCard(TodoItem todo) {
  return Card(
    child: ListTile(
      leading: Checkbox(
        value: todo.isCompleted,
        onChanged: (value) {
          setState(() {
            final index = _todos.indexWhere((t) => t.id == todo.id);
            _todos[index] = todo.copyWith(isCompleted: value);
          });
        },
      ),
      title: Text(
        todo.title,
        style: TextStyle(
          decoration: todo.isCompleted ? TextDecoration.lineThrough : null,
          color: todo.isCompleted ? Colors.grey : null,
        ),
      ),
      // 其他UI组件...
    ),
  );
}

3. 日期处理功能

日期格式化
String _formatDate(DateTime date) {
  return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}

String _formatDueDate(DateTime dueDate) {
  final now = DateTime.now();
  final today = DateTime(now.year, now.month, now.day);
  final due = DateTime(dueDate.year, dueDate.month, dueDate.day);
  final difference = due.difference(today).inDays;

  if (difference < 0) {
    return '已逾期';
  } else if (difference == 0) {
    return '今天';
  } else if (difference == 1) {
    return '明天';
  } else {
    return '${difference}天后';
  }
}
逾期检测
bool get isOverdue {
  return dueDate != null && 
         dueDate!.isBefore(DateTime.now()) && 
         !isCompleted;
}

UI组件设计

1. 主页面布局

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

Widget build(BuildContext context) {
  return Scaffold(
    body: [
      _buildTodoListPage(),    // 待办列表
      _buildPriorityPage(),    // 优先级管理
      _buildStatsPage(),       // 统计分析
    ][_selectedIndex],
    bottomNavigationBar: NavigationBar(
      selectedIndex: _selectedIndex,
      destinations: const [
        NavigationDestination(
          icon: Icon(Icons.list_outlined),
          selectedIcon: Icon(Icons.list),
          label: '待办',
        ),
        NavigationDestination(
          icon: Icon(Icons.priority_high_outlined),
          selectedIcon: Icon(Icons.priority_high),
          label: '优先级',
        ),
        NavigationDestination(
          icon: Icon(Icons.analytics_outlined),
          selectedIcon: Icon(Icons.analytics),
          label: '统计',
        ),
      ],
    ),
    floatingActionButton: _selectedIndex == 0
        ? FloatingActionButton(
            onPressed: _showAddTodoDialog,
            child: const Icon(Icons.add),
          )
        : null,
  );
}

2. 任务卡片设计

任务卡片组件
Widget _buildTodoCard(TodoItem todo) {
  final isOverdue = todo.dueDate != null && 
      todo.dueDate!.isBefore(DateTime.now()) && 
      !todo.isCompleted;

  return Card(
    margin: const EdgeInsets.only(bottom: 8),
    child: ListTile(
      // 完成状态复选框
      leading: Checkbox(
        value: todo.isCompleted,
        onChanged: (value) => _toggleTodoStatus(todo, value),
      ),
      
      // 任务标题
      title: Text(
        todo.title,
        style: TextStyle(
          decoration: todo.isCompleted ? TextDecoration.lineThrough : null,
          color: todo.isCompleted ? Colors.grey : null,
        ),
      ),
      
      // 任务详情
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          if (todo.description.isNotEmpty)
            Text(todo.description),
          const SizedBox(height: 4),
          Row(
            children: [
              // 优先级标签
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                decoration: BoxDecoration(
                  color: PriorityConfig.colors[todo.priority],
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Text(
                  PriorityConfig.names[todo.priority] ?? '',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 10,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
              
              // 截止日期显示
              if (todo.dueDate != null) ...[
                const SizedBox(width: 8),
                Icon(
                  isOverdue ? Icons.warning : Icons.schedule,
                  size: 14,
                  color: isOverdue ? Colors.red : Colors.grey,
                ),
                const SizedBox(width: 2),
                Text(
                  _formatDueDate(todo.dueDate!),
                  style: TextStyle(
                    fontSize: 12,
                    color: isOverdue ? Colors.red : Colors.grey,
                    fontWeight: isOverdue ? FontWeight.bold : null,
                  ),
                ),
              ],
            ],
          ),
        ],
      ),
      
      // 操作菜单
      trailing: PopupMenuButton(
        itemBuilder: (context) => [
          PopupMenuItem(
            value: 'edit',
            child: const Row(
              children: [
                Icon(Icons.edit, size: 18),
                SizedBox(width: 8),
                Text('编辑'),
              ],
            ),
          ),
          PopupMenuItem(
            value: 'delete',
            child: const Row(
              children: [
                Icon(Icons.delete, size: 18, color: Colors.red),
                SizedBox(width: 8),
                Text('删除', style: TextStyle(color: Colors.red)),
              ],
            ),
          ),
        ],
        onSelected: (value) {
          if (value == 'edit') {
            _showEditTodoDialog(todo);
          } else if (value == 'delete') {
            _deleteTodo(todo.id);
          }
        },
      ),
    ),
  );
}

3. 优先级筛选器

Widget _buildPriorityFilter() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '选择优先级',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 12),
          Wrap(
            spacing: 8,
            children: Priority.values.map((priority) {
              final isSelected = _filterPriority == priority;
              final count = _getTodosByPriority(priority).length;
              
              return FilterChip(
                selected: isSelected,
                label: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(PriorityConfig.names[priority] ?? ''),
                    const SizedBox(width: 4),
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                      decoration: BoxDecoration(
                        color: isSelected ? Colors.white : PriorityConfig.colors[priority],
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: Text(
                        count.toString(),
                        style: TextStyle(
                          fontSize: 10,
                          color: isSelected ? PriorityConfig.colors[priority] : Colors.white,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ],
                ),
                selectedColor: PriorityConfig.colors[priority]?.withOpacity(0.2),
                onSelected: (selected) {
                  setState(() {
                    _filterPriority = priority;
                  });
                },
              );
            }).toList(),
          ),
        ],
      ),
    ),
  );
}

4. 统计图表组件

完成率圆形图表
Widget _buildCompletionChart(int completed, int total) {
  return Center(
    child: SizedBox(
      width: 120,
      height: 120,
      child: Stack(
        children: [
          CircularProgressIndicator(
            value: total > 0 ? completed / total : 0,
            strokeWidth: 12,
            backgroundColor: Colors.grey.shade300,
            valueColor: const AlwaysStoppedAnimation<Color>(Colors.green),
          ),
          Center(
            child: Text(
              '${total > 0 ? (completed / total * 100).toStringAsFixed(1) : 0}%',
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ],
      ),
    ),
  );
}
优先级分布条形图
Widget _buildPriorityDistribution() {
  return Column(
    children: Priority.values.map((priority) {
      final count = _getTodosByPriority(priority).length;
      final total = _todos.length;
      final percentage = total > 0 ? (count / total * 100) : 0.0;
      
      return Padding(
        padding: const EdgeInsets.only(bottom: 12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(PriorityConfig.names[priority] ?? ''),
                Text('$count (${percentage.toStringAsFixed(1)}%)'),
              ],
            ),
            const SizedBox(height: 4),
            LinearProgressIndicator(
              value: total > 0 ? count / total : 0,
              backgroundColor: Colors.grey.shade300,
              valueColor: AlwaysStoppedAnimation<Color>(
                PriorityConfig.colors[priority]!,
              ),
            ),
          ],
        ),
      );
    }).toList(),
  );
}

状态管理

1. 状态变量设计

class _TodoHomePageState extends State<TodoHomePage> {
  int _selectedIndex = 0;              // 当前选中的页面索引
  List<TodoItem> _todos = [];          // 待办事项列表
  Priority _filterPriority = Priority.urgent; // 优先级筛选条件
}

2. 状态更新机制

任务状态切换
void _toggleTodoStatus(TodoItem todo, bool? isCompleted) {
  setState(() {
    final index = _todos.indexWhere((t) => t.id == todo.id);
    if (index != -1) {
      _todos[index] = todo.copyWith(isCompleted: isCompleted);
    }
  });
}
任务添加和编辑
void _saveTodo(TodoItem todo, {bool isEdit = false}) {
  setState(() {
    if (isEdit) {
      final index = _todos.indexWhere((t) => t.id == todo.id);
      if (index != -1) {
        _todos[index] = todo;
      }
    } else {
      _todos.add(todo);
    }
  });
}
任务删除
void _deleteTodo(String id) {
  setState(() {
    _todos.removeWhere((todo) => todo.id == id);
  });
}

3. 数据持久化

虽然当前版本使用内存存储,但可以轻松扩展到本地存储:

// 保存到本地存储
Future<void> _saveTodosToStorage() async {
  final prefs = await SharedPreferences.getInstance();
  final todosJson = _todos.map((todo) => todo.toJson()).toList();
  await prefs.setString('todos', jsonEncode(todosJson));
}

// 从本地存储加载
Future<void> _loadTodosFromStorage() async {
  final prefs = await SharedPreferences.getInstance();
  final todosString = prefs.getString('todos');
  if (todosString != null) {
    final todosJson = jsonDecode(todosString) as List;
    setState(() {
      _todos = todosJson.map((json) => TodoItem.fromJson(json)).toList();
    });
  }
}

工具方法实现

1. 日期处理工具

class DateUtils {
  // 格式化日期为 YYYY-MM-DD 格式
  static String formatDate(DateTime date) {
    return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
  }

  // 计算相对日期描述
  static String getRelativeDateDescription(DateTime dueDate) {
    final now = DateTime.now();
    final today = DateTime(now.year, now.month, now.day);
    final due = DateTime(dueDate.year, dueDate.month, dueDate.day);
    final difference = due.difference(today).inDays;

    if (difference < 0) {
      return '已逾期';
    } else if (difference == 0) {
      return '今天';
    } else if (difference == 1) {
      return '明天';
    } else if (difference <= 7) {
      return '${difference}天后';
    } else {
      return formatDate(dueDate);
    }
  }

  // 检查是否逾期
  static bool isOverdue(DateTime? dueDate, bool isCompleted) {
    if (dueDate == null || isCompleted) return false;
    return dueDate.isBefore(DateTime.now());
  }

  // 获取今天的开始时间
  static DateTime getStartOfDay(DateTime date) {
    return DateTime(date.year, date.month, date.day);
  }

  // 获取今天的结束时间
  static DateTime getEndOfDay(DateTime date) {
    return DateTime(date.year, date.month, date.day, 23, 59, 59);
  }
}

2. 优先级工具

class PriorityUtils {
  // 获取优先级颜色
  static Color getPriorityColor(Priority priority) {
    return PriorityConfig.colors[priority] ?? Colors.grey;
  }

  // 获取优先级名称
  static String getPriorityName(Priority priority) {
    return PriorityConfig.names[priority] ?? '未知';
  }

  // 获取优先级权重
  static int getPriorityWeight(Priority priority) {
    return PriorityConfig.weights[priority] ?? 1;
  }

  // 根据字符串获取优先级
  static Priority? getPriorityFromString(String priorityString) {
    for (final priority in Priority.values) {
      if (PriorityConfig.names[priority] == priorityString) {
        return priority;
      }
    }
    return null;
  }

  // 获取下一个优先级
  static Priority getNextPriority(Priority current) {
    final index = Priority.values.indexOf(current);
    final nextIndex = (index + 1) % Priority.values.length;
    return Priority.values[nextIndex];
  }

  // 获取上一个优先级
  static Priority getPreviousPriority(Priority current) {
    final index = Priority.values.indexOf(current);
    final prevIndex = (index - 1 + Priority.values.length) % Priority.values.length;
    return Priority.values[prevIndex];
  }
}

3. 统计工具

class StatisticsUtils {
  // 计算完成率
  static double calculateCompletionRate(List<TodoItem> todos) {
    if (todos.isEmpty) return 0.0;
    final completed = todos.where((todo) => todo.isCompleted).length;
    return completed / todos.length;
  }

  // 获取优先级分布
  static Map<Priority, int> getPriorityDistribution(List<TodoItem> todos) {
    final distribution = <Priority, int>{};
    for (final priority in Priority.values) {
      distribution[priority] = todos.where((todo) => todo.priority == priority).length;
    }
    return distribution;
  }

  // 获取逾期任务数量
  static int getOverdueCount(List<TodoItem> todos) {
    return todos.where((todo) => 
        todo.dueDate != null && 
        todo.dueDate!.isBefore(DateTime.now()) && 
        !todo.isCompleted
    ).length;
  }

  // 获取今日到期任务数量
  static int getTodayDueCount(List<TodoItem> todos) {
    final today = DateTime.now();
    final startOfDay = DateTime(today.year, today.month, today.day);
    final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59);
    
    return todos.where((todo) => 
        todo.dueDate != null && 
        todo.dueDate!.isAfter(startOfDay) && 
        todo.dueDate!.isBefore(endOfDay) && 
        !todo.isCompleted
    ).length;
  }

  // 获取本周完成的任务数量
  static int getWeeklyCompletedCount(List<TodoItem> todos) {
    final now = DateTime.now();
    final weekStart = now.subtract(Duration(days: now.weekday - 1));
    final weekStartDay = DateTime(weekStart.year, weekStart.month, weekStart.day);
    
    return todos.where((todo) => 
        todo.isCompleted && 
        todo.createdAt.isAfter(weekStartDay)
    ).length;
  }
}

功能扩展建议

1. 数据持久化

// 使用 SharedPreferences 实现本地存储
class TodoStorage {
  static const String _todosKey = 'todos';

  static Future<void> saveTodos(List<TodoItem> todos) async {
    final prefs = await SharedPreferences.getInstance();
    final todosJson = todos.map((todo) => todo.toJson()).toList();
    await prefs.setString(_todosKey, jsonEncode(todosJson));
  }

  static Future<List<TodoItem>> loadTodos() async {
    final prefs = await SharedPreferences.getInstance();
    final todosString = prefs.getString(_todosKey);
    if (todosString == null) return [];
    
    final todosJson = jsonDecode(todosString) as List;
    return todosJson.map((json) => TodoItem.fromJson(json)).toList();
  }
}

// TodoItem 添加序列化方法
extension TodoItemSerialization on TodoItem {
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'priority': priority.index,
      'createdAt': createdAt.millisecondsSinceEpoch,
      'dueDate': dueDate?.millisecondsSinceEpoch,
      'isCompleted': isCompleted,
    };
  }

  static TodoItem fromJson(Map<String, dynamic> json) {
    return TodoItem(
      id: json['id'],
      title: json['title'],
      description: json['description'] ?? '',
      priority: Priority.values[json['priority']],
      createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt']),
      dueDate: json['dueDate'] != null 
          ? DateTime.fromMillisecondsSinceEpoch(json['dueDate'])
          : null,
      isCompleted: json['isCompleted'] ?? false,
    );
  }
}

2. 通知提醒功能

class NotificationService {
  static Future<void> scheduleTaskReminder(TodoItem todo) async {
    if (todo.dueDate == null) return;

    final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    
    // 在截止日期前一天提醒
    final reminderTime = todo.dueDate!.subtract(const Duration(days: 1));
    
    await flutterLocalNotificationsPlugin.schedule(
      todo.id.hashCode,
      '任务提醒',
      '任务"${todo.title}"将在明天到期',
      reminderTime,
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'task_reminder',
          '任务提醒',
          channelDescription: '待办事项到期提醒',
          importance: Importance.high,
          priority: Priority.high,
        ),
      ),
    );
  }

  static Future<void> cancelTaskReminder(String todoId) async {
    final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    await flutterLocalNotificationsPlugin.cancel(todoId.hashCode);
  }
}

3. 任务分类功能

enum TaskCategory {
  work,     // 工作
  personal, // 个人
  study,    // 学习
  health,   // 健康
  shopping, // 购物
  other,    // 其他
}

class CategoryConfig {
  static const Map<TaskCategory, String> names = {
    TaskCategory.work: '工作',
    TaskCategory.personal: '个人',
    TaskCategory.study: '学习',
    TaskCategory.health: '健康',
    TaskCategory.shopping: '购物',
    TaskCategory.other: '其他',
  };

  static const Map<TaskCategory, IconData> icons = {
    TaskCategory.work: Icons.work,
    TaskCategory.personal: Icons.person,
    TaskCategory.study: Icons.school,
    TaskCategory.health: Icons.favorite,
    TaskCategory.shopping: Icons.shopping_cart,
    TaskCategory.other: Icons.more_horiz,
  };
}

// 扩展 TodoItem 添加分类字段
class TodoItemWithCategory extends TodoItem {
  final TaskCategory category;

  const TodoItemWithCategory({
    required super.id,
    required super.title,
    super.description = '',
    required super.priority,
    required super.createdAt,
    super.dueDate,
    super.isCompleted = false,
    this.category = TaskCategory.other,
  });
}

4. 搜索和筛选功能

class TodoFilter {
  final String? searchText;
  final Priority? priority;
  final TaskCategory? category;
  final bool? isCompleted;
  final DateRange? dateRange;

  const TodoFilter({
    this.searchText,
    this.priority,
    this.category,
    this.isCompleted,
    this.dateRange,
  });

  List<TodoItem> apply(List<TodoItem> todos) {
    return todos.where((todo) {
      // 文本搜索
      if (searchText != null && searchText!.isNotEmpty) {
        final searchLower = searchText!.toLowerCase();
        if (!todo.title.toLowerCase().contains(searchLower) &&
            !todo.description.toLowerCase().contains(searchLower)) {
          return false;
        }
      }

      // 优先级筛选
      if (priority != null && todo.priority != priority) {
        return false;
      }

      // 完成状态筛选
      if (isCompleted != null && todo.isCompleted != isCompleted) {
        return false;
      }

      // 日期范围筛选
      if (dateRange != null && todo.dueDate != null) {
        if (todo.dueDate!.isBefore(dateRange!.start) ||
            todo.dueDate!.isAfter(dateRange!.end)) {
          return false;
        }
      }

      return true;
    }).toList();
  }
}

class DateRange {
  final DateTime start;
  final DateTime end;

  const DateRange({required this.start, required this.end});
}

5. 数据导入导出功能

class TodoExportService {
  // 导出为 JSON 格式
  static String exportToJson(List<TodoItem> todos) {
    final todosJson = todos.map((todo) => todo.toJson()).toList();
    return jsonEncode({
      'version': '1.0',
      'exportDate': DateTime.now().toIso8601String(),
      'todos': todosJson,
    });
  }

  // 从 JSON 导入
  static List<TodoItem> importFromJson(String jsonString) {
    final data = jsonDecode(jsonString) as Map<String, dynamic>;
    final todosJson = data['todos'] as List;
    return todosJson.map((json) => TodoItem.fromJson(json)).toList();
  }

  // 导出为 CSV 格式
  static String exportToCsv(List<TodoItem> todos) {
    final buffer = StringBuffer();
    buffer.writeln('标题,描述,优先级,创建时间,截止时间,完成状态');
    
    for (final todo in todos) {
      buffer.writeln([
        todo.title,
        todo.description,
        PriorityConfig.names[todo.priority],
        DateUtils.formatDate(todo.createdAt),
        todo.dueDate != null ? DateUtils.formatDate(todo.dueDate!) : '',
        todo.isCompleted ? '已完成' : '未完成',
      ].join(','));
    }
    
    return buffer.toString();
  }
}

性能优化策略

1. 列表优化

// 使用 ListView.builder 优化长列表性能
Widget _buildOptimizedTodoList(List<TodoItem> todos) {
  return ListView.builder(
    itemCount: todos.length,
    itemBuilder: (context, index) {
      final todo = todos[index];
      return _buildTodoCard(todo);
    },
  );
}

// 使用 AutomaticKeepAliveClientMixin 保持页面状态
class _TodoListPageState extends State<TodoListPage> 
    with AutomaticKeepAliveClientMixin {
  
  
  bool get wantKeepAlive => true;

  
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return _buildTodoList();
  }
}

2. 状态管理优化

// 使用 ValueNotifier 优化局部更新
class TodoNotifier extends ValueNotifier<List<TodoItem>> {
  TodoNotifier(List<TodoItem> todos) : super(todos);

  void addTodo(TodoItem todo) {
    value = [...value, todo];
  }

  void updateTodo(String id, TodoItem updatedTodo) {
    final index = value.indexWhere((todo) => todo.id == id);
    if (index != -1) {
      final newList = [...value];
      newList[index] = updatedTodo;
      value = newList;
    }
  }

  void deleteTodo(String id) {
    value = value.where((todo) => todo.id != id).toList();
  }
}

// 在 Widget 中使用
class TodoListWidget extends StatelessWidget {
  final TodoNotifier todoNotifier;

  const TodoListWidget({super.key, required this.todoNotifier});

  
  Widget build(BuildContext context) {
    return ValueListenableBuilder<List<TodoItem>>(
      valueListenable: todoNotifier,
      builder: (context, todos, child) {
        return ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) => _buildTodoCard(todos[index]),
        );
      },
    );
  }
}

3. 内存管理

class TodoMemoryManager {
  static const int maxTodosInMemory = 1000;

  // 清理已完成的旧任务
  static List<TodoItem> cleanupOldCompletedTodos(List<TodoItem> todos) {
    final completedTodos = todos.where((todo) => todo.isCompleted).toList();
    final pendingTodos = todos.where((todo) => !todo.isCompleted).toList();

    // 保留最近30天内完成的任务
    final thirtyDaysAgo = DateTime.now().subtract(const Duration(days: 30));
    final recentCompletedTodos = completedTodos.where((todo) => 
        todo.createdAt.isAfter(thirtyDaysAgo)
    ).toList();

    return [...pendingTodos, ...recentCompletedTodos];
  }

  // 限制内存中的任务数量
  static List<TodoItem> limitTodosInMemory(List<TodoItem> todos) {
    if (todos.length <= maxTodosInMemory) return todos;

    // 优先保留未完成的任务
    final pendingTodos = todos.where((todo) => !todo.isCompleted).toList();
    final completedTodos = todos.where((todo) => todo.isCompleted).toList();

    if (pendingTodos.length >= maxTodosInMemory) {
      return pendingTodos.take(maxTodosInMemory).toList();
    }

    final remainingSlots = maxTodosInMemory - pendingTodos.length;
    final recentCompletedTodos = completedTodos
        .take(remainingSlots)
        .toList();

    return [...pendingTodos, ...recentCompletedTodos];
  }
}

测试指南

1. 单元测试

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('TodoItem Tests', () {
    test('priorityScore calculation should be correct', () {
      final now = DateTime.now();
      
      // 测试基础优先级分数
      final urgentTodo = TodoItem(
        id: '1',
        title: 'Test',
        priority: Priority.urgent,
        createdAt: now,
      );
      expect(urgentTodo.priorityScore, 400); // 4 * 100

      // 测试截止日期影响
      final overdueTodo = TodoItem(
        id: '2',
        title: 'Test',
        priority: Priority.medium,
        createdAt: now,
        dueDate: now.subtract(const Duration(days: 1)),
      );
      expect(overdueTodo.priorityScore, 400); // 2 * 100 + 200
    });

    test('copyWith should update specific fields', () {
      final original = TodoItem(
        id: '1',
        title: 'Original',
        priority: Priority.low,
        createdAt: DateTime.now(),
      );

      final updated = original.copyWith(
        title: 'Updated',
        priority: Priority.high,
      );

      expect(updated.title, 'Updated');
      expect(updated.priority, Priority.high);
      expect(updated.id, original.id); // 不变的字段
    });
  });

  group('Sorting Tests', () {
    test('todos should be sorted by priority score', () {
      final now = DateTime.now();
      final todos = [
        TodoItem(
          id: '1',
          title: 'Low Priority',
          priority: Priority.low,
          createdAt: now,
        ),
        TodoItem(
          id: '2',
          title: 'Urgent',
          priority: Priority.urgent,
          createdAt: now,
        ),
        TodoItem(
          id: '3',
          title: 'Medium',
          priority: Priority.medium,
          createdAt: now,
        ),
      ];

      todos.sort((a, b) => b.priorityScore.compareTo(a.priorityScore));

      expect(todos[0].priority, Priority.urgent);
      expect(todos[1].priority, Priority.medium);
      expect(todos[2].priority, Priority.low);
    });
  });
}

2. Widget 测试

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('TodoCard Widget Tests', () {
    testWidgets('should display todo information correctly', (tester) async {
      final todo = TodoItem(
        id: '1',
        title: 'Test Todo',
        description: 'Test Description',
        priority: Priority.high,
        createdAt: DateTime.now(),
      );

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: TodoCard(todo: todo),
          ),
        ),
      );

      expect(find.text('Test Todo'), findsOneWidget);
      expect(find.text('Test Description'), findsOneWidget);
      expect(find.text('高'), findsOneWidget);
    });

    testWidgets('should toggle completion status', (tester) async {
      bool isCompleted = false;
      final todo = TodoItem(
        id: '1',
        title: 'Test Todo',
        priority: Priority.medium,
        createdAt: DateTime.now(),
        isCompleted: isCompleted,
      );

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: StatefulBuilder(
              builder: (context, setState) {
                return TodoCard(
                  todo: todo,
                  onToggle: (value) {
                    setState(() {
                      isCompleted = value ?? false;
                    });
                  },
                );
              },
            ),
          ),
        ),
      );

      // 点击复选框
      await tester.tap(find.byType(Checkbox));
      await tester.pump();

      expect(isCompleted, true);
    });
  });
}

3. 集成测试

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('Todo App Integration Tests', () {
    testWidgets('complete todo workflow', (tester) async {
      await tester.pumpWidget(const TodoPriorityApp());

      // 1. 验证初始状态
      expect(find.text('待办事项优先级排序'), findsOneWidget);

      // 2. 添加新任务
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pumpAndSettle();

      await tester.enterText(find.byType(TextField).first, '测试任务');
      await tester.tap(find.text('添加'));
      await tester.pumpAndSettle();

      // 3. 验证任务已添加
      expect(find.text('测试任务'), findsOneWidget);

      // 4. 切换到统计页面
      await tester.tap(find.text('统计'));
      await tester.pumpAndSettle();

      // 5. 验证统计信息
      expect(find.text('总体统计'), findsOneWidget);
      expect(find.text('1'), findsOneWidget); // 总任务数
    });
  });
}

部署指南

1. Android 部署

权限配置 (android/app/src/main/AndroidManifest.xml)
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
构建配置 (android/app/build.gradle)
android {
    compileSdkVersion 34
    
    defaultConfig {
        applicationId "com.example.todo_priority"
        minSdkVersion 21
        targetSdkVersion 34
        versionCode 1
        versionName "1.0.0"
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

2. iOS 部署

权限配置 (ios/Runner/Info.plist)
<key>NSUserNotificationUsageDescription</key>
<string>需要通知权限来提醒您的待办事项</string>

3. 构建命令

# 清理项目
flutter clean

# 获取依赖
flutter pub get

# Android APK (调试版)
flutter build apk --debug

# Android APK (发布版)
flutter build apk --release

# Android App Bundle (推荐用于 Google Play)
flutter build appbundle --release

# iOS (发布版)
flutter build ios --release

4. 性能优化构建

# 启用代码混淆和压缩
flutter build apk --release --obfuscate --split-debug-info=build/debug-info

# 构建不同架构的 APK
flutter build apk --release --split-per-abi

项目总结

待办事项优先级排序应用是一个功能完整的任务管理工具,展示了以下技术要点:

技术亮点

  1. 智能排序算法:综合考虑优先级和时间因素的动态排序
  2. Material Design 3:现代化的UI设计和交互体验
  3. 状态管理:高效的状态更新和数据流管理
  4. 日期处理:完善的日期格式化和相对时间计算
  5. 数据建模:清晰的数据结构和业务逻辑封装

学习价值

  • 算法设计:学习优先级排序算法的设计和实现
  • 状态管理:掌握Flutter中的状态管理最佳实践
  • UI设计:了解现代移动应用的界面设计原则
  • 数据处理:学习日期时间处理和数据格式化技巧

扩展方向

  1. 数据持久化:集成本地数据库存储
  2. 云端同步:支持多设备数据同步
  3. 通知提醒:添加本地通知功能
  4. 主题定制:支持多种主题和个性化设置
  5. 数据分析:提供更详细的任务完成统计

这个项目为学习Flutter应用开发提供了完整的实践案例,涵盖了状态管理、UI设计、算法实现等多个方面,是一个优秀的学习和参考项目。

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐