Flutter多级可重排嵌套列表实现求助:任务管理工具开发难题
实现Flutter多级嵌套拖拽任务列表方案
核心思路
要支持4-5级甚至无限级嵌套,得从递归数据结构、拖拽位置判定、递归UI渲染三个核心点入手——你之前尝试的Stack上层ListView方案容易触发手势冲突,且层级判定逻辑复杂,直接用「每个列表项同时作为拖拽源+拖拽目标」的方式更可靠。
1. 定义递归数据结构
首先用可递归的模型存储任务,每个任务包含自身信息和子任务列表,支持深拷贝避免状态异常:
class Task { final String id; String title; List<Task> children; Task({ required this.id, required this.title, this.children = const [], }); // 深拷贝方法,防止拖拽时修改原数据引发状态混乱 Task copyWith({ String? id, String? title, List<Task>? children, }) { return Task( id: id ?? this.id, title: title ?? this.title, children: children ?? List.from(this.children), ); } }
2. 拖拽逻辑实现(LongPressDraggable + DragTarget)
每个列表项同时承担「可拖拽源」和「拖拽目标」角色,通过拖拽位置判断操作类型:
- 拖拽进入目标项右侧1/3区域:判定为嵌套(成为子项)
- 进入左侧2/3区域:判定为同层级重排
关键代码片段
class TaskItem extends StatefulWidget { final Task task; final int level; final Function(Task, Task?) onReorder; // 参数:被拖拽项、目标父项(null则为同层级) final Function(Task) onTap; const TaskItem({ super.key, required this.task, required this.level, required this.onReorder, required this.onTap, }); @override State<TaskItem> createState() => _TaskItemState(); } class _TaskItemState extends State<TaskItem> { bool _isDragging = false; bool _isAcceptingNested = false; @override Widget build(BuildContext context) { final indent = widget.level * 24.0; // 层级缩进,视觉区分层级 return DragTarget<Task>( onWillAccept: (data) { if (data?.id == widget.task.id) return false; // 禁止拖到自己身上 // 判断拖拽位置是否在右侧区域 final renderBox = context.findRenderObject() as RenderBox; final dragPos = DragStartDetails.globalPosition; final relativeX = dragPos.dx - renderBox.localToGlobal(Offset.zero).dx; _isAcceptingNested = relativeX > renderBox.size.width * 0.66; return true; }, onAccept: (draggedTask) { if (_isAcceptingNested) { widget.onReorder(draggedTask, widget.task); // 嵌套操作 } else { widget.onReorder(draggedTask, null); // 同层级重排 } _isAcceptingNested = false; }, builder: (context, candidateData, rejectedData) { return LongPressDraggable<Task>( data: widget.task, onDragStarted: () => setState(() => _isDragging = true), onDragEnd: (_) => setState(() => _isDragging = false), feedback: Material( elevation: 4, child: Container( width: MediaQuery.of(context).size.width - indent, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), color: Colors.grey[200], child: Text(widget.task.title), ), ), child: Opacity( opacity: _isDragging ? 0.5 : 1, child: InkWell( onTap: () => widget.onTap(widget.task), child: Container( padding: EdgeInsets.only(left: indent, right: 16, top: 12, bottom: 12), decoration: BoxDecoration( border: _isAcceptingNested ? Border(right: BorderSide(color: Colors.blue, width: 3)) : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: Text(widget.task.title)), if (widget.task.children.isNotEmpty) Icon(Icons.keyboard_arrow_down), ], ), ), ), ), ); }, ); } }
3. 递归渲染列表
用递归组件渲染所有层级任务,同时处理展开/折叠状态:
class NestedTaskList extends StatefulWidget { final List<Task> tasks; final int level; final Function(Task, Task?) onReorder; const NestedTaskList({ super.key, required this.tasks, this.level = 0, required this.onReorder, }); @override State<NestedTaskList> createState() => _NestedTaskListState(); } class _NestedTaskListState extends State<NestedTaskList> { final Map<String, bool> _expanded = {}; // 记录任务展开状态 @override void initState() { super.initState(); // 初始化所有任务为展开状态 for (var task in widget.tasks) { _expanded[task.id] = true; } } void _toggleExpand(String taskId) { setState(() { _expanded[taskId] = !(_expanded[taskId] ?? true); }); } @override Widget build(BuildContext context) { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: widget.tasks.length, itemBuilder: (context, index) { final task = widget.tasks[index]; return Column( children: [ TaskItem( task: task, level: widget.level, onReorder: (draggedTask, targetParent) { setState(() { _reorderTasks(draggedTask, targetParent, task); }); }, onTap: (task) => _toggleExpand(task.id), ), if (_expanded[task.id] == true && task.children.isNotEmpty) NestedTaskList( tasks: task.children, level: widget.level + 1, onReorder: widget.onReorder, ), ], ); }, ); } void _reorderTasks(Task draggedTask, Task? targetParent, Task targetSibling) { // 1. 从原位置移除拖拽项 void removeTask(List<Task> tasks) { tasks.removeWhere((t) => t.id == draggedTask.id); for (var task in tasks) { if (task.children.isNotEmpty) removeTask(task.children); } } removeTask(widget.tasks); // 2. 添加到目标位置 if (targetParent != null) { targetParent.children.add(draggedTask.copyWith()); // 嵌套 } else { final index = widget.tasks.indexOf(targetSibling); widget.tasks.insert(index, draggedTask.copyWith()); // 同层级重排 } } }
4. 页面中使用示例
class TaskManagerPage extends StatefulWidget { const TaskManagerPage({super.key}); @override State<TaskManagerPage> createState() => _TaskManagerPageState(); } class _TaskManagerPageState extends State<TaskManagerPage> { List<Task> tasks = [ Task(id: '1', title: '一级任务1', children: [ Task(id: '1-1', title: '二级任务1-1'), Task(id: '1-2', title: '二级任务1-2', children: [ Task(id: '1-2-1', title: '三级任务1-2-1', children: [ Task(id: '1-2-1-1', title: '四级任务1-2-1-1'), ]), ]), ]), Task(id: '2', title: '一级任务2'), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('任务管理')), body: NestedTaskList( tasks: tasks, onReorder: (draggedTask, targetParent) { setState(() { // 可结合Provider/Riverpod等状态管理优化全局数据更新 }); }, ), ); } }
优化提示
- 状态管理:大型应用建议用Provider/Riverpod替代setState,避免深层递归导致的状态更新卡顿
- 性能优化:超大量级任务列表可添加懒加载,避免一次性渲染所有层级
- 拖拽反馈:可优化拖拽预览的样式,添加层级缩进让用户更直观感知目标层级
内容的提问来源于stack exchange,提问作者Nicolas Essipova
相关产品推荐
相关产品推荐

