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

Flutter:从Drawer拖拽Draggable到Scaffold主体区域的问题

解决Flutter中Drawer内Draggable拖拽到主区域的问题

问题根源在于:Drawer是一个独立的路由页面,调用Navigator.pop会直接销毁Drawer的上下文,而Draggable的初始渲染依赖该上下文,导致拖拽流程中断。

下面提供两种可行的解决方案:


方案1:延迟关闭Drawer(简单易实现)

利用Flutter的帧调度机制,在拖拽启动后的下一帧再关闭Drawer,此时Draggable的拖拽状态已经完成初始化,不再依赖Drawer的上下文。

修改代码中的onDragStarted回调:

onDragStarted: () {
  // 在下一帧执行关闭Drawer操作,避免中断拖拽初始化
  SchedulerBinding.instance.addPostFrameCallback((_) {
    Navigator.pop(context);
  });
},

需要在文件顶部导入调度器依赖:

import 'package:flutter/scheduler.dart';

方案2:使用Overlay承载拖拽反馈(更稳定)

将拖拽的feedback组件放到全局Overlay中,完全脱离Drawer的上下文,这样关闭Drawer后,反馈仍能正常显示,拖拽流程不受影响。

修改后的完整代码

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String droppedItem = "";
  OverlayEntry? _overlayEntry;
  final GlobalKey _overlayKey = GlobalKey();
  Offset _dragOffset = Offset.zero;

  // 创建Overlay中的反馈组件
  OverlayEntry _createOverlayFeedback(Widget feedback) {
    return OverlayEntry(
      builder: (context) => Positioned(
        left: _dragOffset.dx - 25,
        top: _dragOffset.dy - 25,
        child: feedback,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _overlayKey,
      appBar: AppBar(
        title: const Text('Drag and Drop Example'),
      ),
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.grey,
          child: DragTarget<String>(
            onWillAcceptWithDetails: (details) {
              setState(() {
                droppedItem = details.data;
              });
              return true;
            },
            onLeave: (data) {
              _overlayEntry?.remove();
              _overlayEntry = null;
            },
            onAcceptWithDetails: (details) {
              _overlayEntry?.remove();
              _overlayEntry = null;
            },
            builder: (context, candidateData, rejectedData) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  const Icon(
                    Icons.cloud_upload,
                    size: 50,
                    color: Colors.blue,
                  ),
                  Text(
                    'Drop Here : \n $droppedItem',
                    style: const TextStyle(fontSize: 20),
                  ),
                ],
              );
            },
          ),
        ),
      ),
      drawer: Drawer(
        child: ListView(
          children: <Widget>[
            Draggable<String>(
              data: 'Item 1',
              feedback: const SizedBox.shrink(), // 原反馈不再显示
              childWhenDragging: const ListTile(
                title: Text('Item 1'),
                leading: Icon(Icons.drag_handle),
              ),
              child: const ListTile(
                title: Text('Item 1'),
                leading: Icon(Icons.drag_handle),
              ),
              onDragStarted: () {
                // 记录初始拖拽位置
                RenderBox box = context.findRenderObject() as RenderBox;
                _dragOffset = box.localToGlobal(Offset.zero);
                // 插入Overlay反馈
                _overlayEntry = _createOverlayFeedback(
                  Container(
                    color: Colors.deepOrange,
                    height: 50,
                    width: 50,
                    child: const Icon(Icons.directions_run),
                  ),
                );
                Overlay.of(_overlayKey.currentContext!)?.insert(_overlayEntry!);
                // 关闭Drawer
                Navigator.pop(context);
              },
              onDragUpdate: (details) {
                // 更新拖拽位置
                setState(() {
                  _dragOffset = details.globalPosition;
                });
                _overlayEntry?.markNeedsBuild();
              },
              onDragEnd: (details) {
                _overlayEntry?.remove();
                _overlayEntry = null;
              },
            ),
            Draggable<String>(
              data: 'Item 2',
              feedback: const SizedBox.shrink(),
              childWhenDragging: const ListTile(
                title: Text('Item 2'),
                leading: Icon(Icons.drag_handle),
              ),
              child: const ListTile(
                title: Text('Item 2'),
                leading: Icon(Icons.drag_handle),
              ),
              onDragStarted: () {
                RenderBox box = context.findRenderObject() as RenderBox;
                _dragOffset = box.localToGlobal(Offset.zero);
                _overlayEntry = _createOverlayFeedback(
                  Container(
                    color: Colors.deepOrange,
                    height: 50,
                    width: 50,
                    child: const Icon(Icons.directions_run),
                  ),
                );
                Overlay.of(_overlayKey.currentContext!)?.insert(_overlayEntry!);
                Navigator.pop(context);
              },
              onDragUpdate: (details) {
                setState(() {
                  _dragOffset = details.globalPosition;
                });
                _overlayEntry?.markNeedsBuild();
              },
              onDragEnd: (details) {
                _overlayEntry?.remove();
                _overlayEntry = null;
              },
            ),
          ],
        ),
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Stefan van der Lee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:35:15