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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:17:13