You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Flutter创建看板应用并实现拖放功能?

Flutter 看板拖拽实现方案

下面是实现包含"To Do"、"In Progress"、"Done"三列且支持任务跨列拖拽的完整代码示例,基于Flutter原生组件实现,无需额外依赖:

1. 定义任务数据模型

class Task {
  final String id;
  final String title;
  final String status; // 对应 "todo"、"in_progress"、"done"

  Task({required this.id, required this.title, required this.status});

  // 复制方法,用于更新任务状态
  Task copyWith({String? status}) {
    return Task(
      id: id,
      title: title,
      status: status ?? this.status,
    );
  }
}

2. 主看板页面实现

import 'package:flutter/material.dart';

class KanbanBoard extends StatefulWidget {
  const KanbanBoard({super.key});

  @override
  State<KanbanBoard> createState() => _KanbanBoardState();
}

class _KanbanBoardState extends State<KanbanBoard> {
  // 初始化各列任务数据
  List<Task> _todoTasks = [
    Task(id: '1', title: '完成Flutter拖拽功能', status: 'todo'),
    Task(id: '2', title: '设计任务卡片样式', status: 'todo'),
  ];
  List<Task> _inProgressTasks = [
    Task(id: '3', title: '编写数据模型', status: 'in_progress'),
  ];
  List<Task> _doneTasks = [
    Task(id: '4', title: '创建Flutter项目', status: 'done'),
  ];

  // 处理任务转移:从原列表移除,添加到目标列表
  void _moveTask(Task task, String newStatus) {
    setState(() {
      // 从原状态列表移除
      switch (task.status) {
        case 'todo':
          _todoTasks.remove(task);
          break;
        case 'in_progress':
          _inProgressTasks.remove(task);
          break;
        case 'done':
          _doneTasks.remove(task);
          break;
      }

      // 添加到新状态列表
      final updatedTask = task.copyWith(status: newStatus);
      switch (newStatus) {
        case 'todo':
          _todoTasks.add(updatedTask);
          break;
        case 'in_progress':
          _inProgressTasks.add(updatedTask);
          break;
        case 'done':
          _doneTasks.add(updatedTask);
          break;
      }
    });
  }

  // 构建任务卡片(可拖拽)
  Widget _buildTaskCard(Task task) {
    return Draggable<Task>(
      data: task,
      feedback: Material(
        elevation: 4,
        child: Container(
          width: 280,
          padding: const EdgeInsets.all(16),
          margin: const EdgeInsets.symmetric(vertical: 8),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(8),
            boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
          ),
          child: Text(task.title, style: const TextStyle(fontSize: 16)),
        ),
      ),
      childWhenDragging: Container(
        height: 60,
        margin: const EdgeInsets.symmetric(vertical: 8),
        decoration: BoxDecoration(
          color: Colors.grey[200],
          borderRadius: BorderRadius.circular(8),
        ),
      ),
      child: Container(
        padding: const EdgeInsets.all(16),
        margin: const EdgeInsets.symmetric(vertical: 8),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(8),
          boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
        ),
        child: Text(task.title, style: const TextStyle(fontSize: 16)),
      ),
    );
  }

  // 构建看板列(可接收拖拽)
  Widget _buildKanbanColumn(String title, String status, List<Task> tasks) {
    return Expanded(
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 8),
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.grey[100],
          borderRadius: BorderRadius.circular(8),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            Expanded(
              child: DragTarget<Task>(
                onAccept: (task) {
                  _moveTask(task, status);
                },
                builder: (context, candidateData, rejectedData) {
                  return ListView.builder(
                    itemCount: tasks.length,
                    itemBuilder: (context, index) {
                      return _buildTaskCard(tasks[index]);
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('项目任务看板')),
      body: Row(
        children: [
          _buildKanbanColumn('To Do', 'todo', _todoTasks),
          _buildKanbanColumn('In Progress', 'in_progress', _inProgressTasks),
          _buildKanbanColumn('Done', 'done', _doneTasks),
        ],
      ),
    );
  }
}

3. 关键功能说明

  • 拖拽组件:使用Draggable包裹任务卡片,data字段传递任务对象,feedback定义拖拽时的预览样式,childWhenDragging定义拖拽时原位置的占位样式。
  • 接收组件:使用DragTarget包裹列内的任务列表,onAccept方法处理拖拽完成后的任务转移逻辑,通过setState更新列表刷新UI。
  • 状态管理:这里用StatefulWidget的本地状态管理任务列表,实际项目中可根据需求替换为Provider、Riverpod或Bloc等状态管理方案。

4. 扩展功能建议

  • 如果需要支持同列内任务排序,可以将ListView.builder替换为ReorderableListView,结合onReorder方法调整列表顺序。
  • 可以添加任务添加/删除功能,通过弹窗或底部输入框实现。
  • 支持任务详情展示,点击任务卡片跳转详情页面。

内容的提问来源于stack exchange,提问作者DarkSistem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 08:06:32