鸿蒙Flutter实战:待办事项三态筛选器

前言

待办列表的典型场景是:用户有 20 条待办,其中 5 条已经完成了。他需要能在"全部待办"和"未完成"和"已完成"三种视图之间快速切换。这是 GTD 方法论中"聚焦当下"的核心需求------已完成的条目不应干扰未完成事项的浏览。

鸿蒙 Flutter 备忘录的待办模块实现了一个简洁的三态筛选器:All / Active / Completed。本文将拆解从枚举定义、UI 切换、到 Provider 数据过滤的完整链路。

项目仓库:todo_flutter_harmony

筛选枚举

dart 复制代码
enum TodoFilter {
  all,       // 全部
  active,    // 进行中(未完成)
  completed, // 已完成
}

枚举比字符串更安全------编译器会检查 switch 的完整性,IDE 也能提供自动补全。

模型定义

dart 复制代码
class Todo {
  final int? id;
  final String title;
  final String? note;
  final bool isCompleted;
  final DateTime? dueDate;
  final DateTime createdAt;

  const Todo({
    this.id,
    required this.title,
    this.note,
    this.isCompleted = false,
    this.dueDate,
    required this.createdAt,
  });

  // 是否已过期
  bool get isOverdue {
    if (isCompleted) return false;
    if (dueDate == null) return false;
    return DateTime.now().isAfter(dueDate!);
  }

  Map<String, dynamic> toMap() => {
    'id': id,
    'title': title,
    'note': note,
    'isCompleted': isCompleted ? 1 : 0,  // SQLite 兼容性
    'dueDate': dueDate?.millisecondsSinceEpoch,
    'createdAt': createdAt.millisecondsSinceEpoch,
  };

  factory Todo.fromMap(Map<String, dynamic> map) => Todo(
    id: map['id'],
    title: map['title'] ?? '',
    note: map['note'],
    isCompleted: (map['isCompleted'] ?? 0) == 1,
    dueDate: map['dueDate'] != null
        ? DateTime.fromMillisecondsSinceEpoch(map['dueDate'])
        : null,
    createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt']),
  );
}

TodoProvider:筛选逻辑

dart 复制代码
class TodoProvider extends ChangeNotifier {
  List<Todo> _todos = [];
  TodoFilter _filter = TodoFilter.all;

  List<Todo> get todos => _todos;
  TodoFilter get filter => _filter;

  List<Todo> get filteredTodos {
    switch (_filter) {
      case TodoFilter.all:
        return List.unmodifiable(_todos);
      case TodoFilter.active:
        return _todos.where((t) => !t.isCompleted).toList();
      case TodoFilter.completed:
        return _todos.where((t) => t.isCompleted).toList();
    }
  }

  // 各状态的数量
  int get totalCount => _todos.length;
  int get activeCount => _todos.where((t) => !t.isCompleted).length;
  int get completedCount => _todos.where((t) => t.isCompleted).length;

  void setFilter(TodoFilter newFilter) {
    _filter = newFilter;
    notifyListeners();
  }

  void loadTodos() async {
    _todos = await DatabaseHelper.instance.getAllTodos();
    _todos.sort((a, b) => b.createdAt.compareTo(a.createdAt));
    notifyListeners();
  }

  Future<void> toggleTodo(int id) async {
    final todo = _todos.firstWhere((t) => t.id == id);
    final updated = todo.copyWith(isCompleted: !todo.isCompleted);
    await DatabaseHelper.instance.updateTodo(updated);
    await loadTodos();
  }

  Future<void> addTodo(Todo todo) async {
    await DatabaseHelper.instance.insertTodo(todo);
    await loadTodos();
  }

  Future<void> deleteTodo(int id) async {
    await DatabaseHelper.instance.deleteTodo(id);
    await loadTodos();
  }
}

UI:筛选切换器

使用 Material 3 的 SegmentedButton 构建三态切换:

dart 复制代码
class TodoFilterBar extends StatelessWidget {
  const TodoFilterBar({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<TodoProvider>(
      builder: (context, provider, _) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: SegmentedButton<TodoFilter>(
            segments: [
              ButtonSegment<TodoFilter>(
                value: TodoFilter.all,
                label: Text('全部 (${provider.totalCount})'),
                icon: const Icon(Icons.list, size: 18),
              ),
              ButtonSegment<TodoFilter>(
                value: TodoFilter.active,
                label: Text('进行中 (${provider.activeCount})'),
                icon: Icon(Icons.radio_button_unchecked, size: 18,
                    color: Colors.orange.shade600),
              ),
              ButtonSegment<TodoFilter>(
                value: TodoFilter.completed,
                label: Text('已完成 (${provider.completedCount})'),
                icon: Icon(Icons.check_circle_outline, size: 18,
                    color: Colors.green.shade600),
              ),
            ],
            selected: {provider.filter},
            onSelectionChanged: (selected) {
              provider.setFilter(selected.first);
            },
            style: ButtonStyle(
              visualDensity: VisualDensity.compact,
            ),
          ),
        );
      },
    );
  }
}

关键细节:每个筛选项的 label 都带了实时数量------"全部 (15)"、"进行中 (10)"、"已完成 (5)"。这给用户在点击前就提供了信息参考。

待办列表页

dart 复制代码
class TodoListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TodoFilterBar(),
        const Divider(height: 1),
        Expanded(
          child: Consumer<TodoProvider>(
            builder: (context, provider, _) {
              final todos = provider.filteredTodos;

              if (todos.isEmpty) {
                return _buildEmptyState(provider.filter);
              }

              return ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  return AnimatedListItem(
                    delay: index * 50,
                    child: _buildTodoItem(todos[index], provider),
                  );
                },
              );
            },
          ),
        ),
      ],
    );
  }

  Widget _buildEmptyState(TodoFilter filter) {
    String message;
    IconData icon;

    switch (filter) {
      case TodoFilter.all:
        message = '暂无待办事项';
        icon = Icons.inbox_outlined;
        break;
      case TodoFilter.active:
        message = '所有待办已完成!';
        icon = Icons.celebration_outlined;
        break;
      case TodoFilter.completed:
        message = '暂无已完成事项';
        icon = Icons.checklist_outlined;
        break;
    }

    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icon, size: 72, color: Colors.grey.shade300),
          const SizedBox(height: 12),
          Text(message, style: TextStyle(
            fontSize: 16, color: Colors.grey.shade500,
          )),
        ],
      ),
    );
  }
}

三种筛选状态各有不同的空状态提示------"进行中"为空时显示庆祝图标,比千篇一律的"暂无数据"友好得多。

待办卡片

dart 复制代码
Widget _buildTodoItem(Todo todo, TodoProvider provider) {
  return SlideActionTile(
    leftActions: [
      SlideAction(
        label: '删除',
        icon: Icons.delete_outline,
        color: Colors.red,
        onTap: () => provider.deleteTodo(todo.id!),
      ),
    ],
    rightActions: [
      SlideAction(
        label: todo.isCompleted ? '撤销' : '完成',
        icon: todo.isCompleted ? Icons.undo : Icons.check,
        color: todo.isCompleted ? Colors.orange : Colors.green,
        onTap: () => provider.toggleTodo(todo.id!),
      ),
    ],
    child: Card(
      elevation: 1,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: Padding(
        padding: const EdgeInsets.all(14),
        child: Row(
          children: [
            // 完成状态复选框
            GestureDetector(
              onTap: () => provider.toggleTodo(todo.id!),
              child: Icon(
                todo.isCompleted
                    ? Icons.check_circle
                    : Icons.radio_button_unchecked,
                color: todo.isCompleted
                    ? const Color(0xFF4DB6AC)
                    : Colors.grey.shade400,
                size: 24,
              ),
            ),
            const SizedBox(width: 12),
            // 标题和备注
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    todo.title,
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                      decoration: todo.isCompleted
                          ? TextDecoration.lineThrough
                          : null,
                      color: todo.isCompleted
                          ? Colors.grey.shade500
                          : Colors.black87,
                    ),
                  ),
                  if (todo.dueDate != null) ...[
                    const SizedBox(height: 4),
                    Row(
                      children: [
                        Icon(Icons.calendar_today, size: 13,
                            color: todo.isOverdue ? Colors.red : Colors.grey),
                        const SizedBox(width: 4),
                        Text(
                          DateFormat('MM-dd').format(todo.dueDate!),
                          style: TextStyle(
                            fontSize: 12,
                            color: todo.isOverdue ? Colors.red : Colors.grey,
                            fontWeight: todo.isOverdue
                                ? FontWeight.w600
                                : FontWeight.normal,
                          ),
                        ),
                      ],
                    ),
                  ],
                ],
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

视觉细节:

  • 已完成的待办:标题加删除线 + 变灰
  • 过期的待办:截止日期变红色加粗
  • 点击圆圈图标即可切换完成状态

鸿蒙兼容性

三态筛选器完全在 Flutter 层实现:

  • SegmentedButton:Material 3 组件
  • TodoFilter 枚举 + filteredTodos getter:纯 Dart 逻辑
  • Provider 响应式更新:Flutter 框架层

零原生依赖,鸿蒙 OHOS 上直接可用。

总结

待办事项三态筛选器的实现可以浓缩为:

  1. 数据层TodoFilter 枚举定义三种状态,filteredTodos getter 用 where() 做内存过滤
  2. UI 层 :Material 3 SegmentedButton 三段式切换,带实时数量统计
  3. 交互层:空状态按筛选类型差异化展示,复选框 + 删除线区分完成态

整个筛选逻辑的核心只有 10 行代码,却让待办列表的可用性上升了一个台阶。

完整项目代码见:todo_flutter_harmony

相关推荐
李宏伟~13 小时前
flutter实现直播推流端
flutter
●VON14 小时前
鸿蒙Flutter实战:多选批量删除模式的实现
flutter·华为·harmonyos·鸿蒙
枫叶丹414 小时前
【HarmonyOS 6.0】Live View Kit 实况支持显示夕阳和赏月背景的技术解读与实践
开发语言·华为·harmonyos
不羁的木木14 小时前
ArkUI实战演练03-常用组件与布局实战
harmonyos
不羁的木木14 小时前
ArkUI实战演练05-动画手势与综合实战
harmonyos
nashane14 小时前
HarmonyOS 6学习:解决非媒体文件下载后用户不可见的问题
学习·华为·harmonyos
云杰zd14 小时前
鸿蒙中实现果壳风格液态TabBar
华为·harmonyos
互联网散修14 小时前
鸿蒙实战:音频波纹动画 —— 录制与播放双模式完整实现
华为·harmonyos·音频录制·音频波纹
坚果的博客14 小时前
Flutter 三方库(Flutter-New-Badge)适配开源鸿蒙教程
flutter·开源·harmonyos