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

Flutter导航时持久化Drawer关闭触发断言异常的解决办法

问题:持久化Drawer关闭时触发Overlay断言异常

我需要实现Drawer的持久化,即切换页面导航时Drawer保持打开状态。为此实现了一个接收GlobalKey参数的自定义Scaffold,代码如下:

MyScaffold(
  // ...
  late GlobalKey<ScaffoldState> _scaffoldKey;

  @override
  void initState() {
    super.initState();
    _scaffoldKey = widget.scaffoldKey;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      drawer: MouseRegion(
        onExit: (_) {
          _scaffoldKey.currentState?.closeDrawer();
        },
        child: Drawer(/* ... */),
      ),
      body: /* ... */,
    );
  }
)

功能逻辑正常,但关闭Drawer时会触发如下断言异常:

2024-03-11 14:05:17.401891: 'package:flutter/src/widgets/overlay.dart': Failed assertion: line 190 pos 12: '!_disposedByOwner': is not true.
2024-03-11 14:05:17.402279: #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
           #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
           #2      OverlayEntry.markNeedsBuild (package:flutter/src/widgets/overlay.dart:190:12)
           #3      ModalRoute.changedInternalState (package:flutter/src/widgets/routes.dart:1711:19)
           #4      LocalHistoryRoute.removeLocalHistoryEntry (package:flutter/src/widgets/routes.dart:715:9)
           #5      LocalHistoryEntry.remove (package:flutter/src/widgets/routes.dart:519:13)
           #6      DrawerControllerState._animationStatusChanged (package:flutter/src/material/drawer.dart:527:24)
           #7      AnimationLocalStatusListenersMixin.notifyStatusListeners (package:flutter/src/animation/listener_helpers.dart:240:19)
           #8      AnimationController._checkStatusChanged (package:flutter/src/animation/animation_controller.dart:815:7)
           #9      AnimationController._startSimulation (package:flutter/src/animation/animation_controller.dart:749:5)
           #10     AnimationController.fling (package:flutter/src/animation/animation_controller.dart:712:12)
           #11     DrawerControllerState.close (package:flutter/src/material/drawer.dart:630:17)
           #12     ScaffoldState.closeDrawer (package:flutter/src/material/scaffold.dart:2271:31)
           #13     TigerScaffoldState.build.<anonymous closure>.<anonymous closure> (package:tigerui/widgets/tiger_scaffold.dart:157:50)
           #14     MouseTracker._handleDeviceUpdateMouseEvents.<anonymous closure> (package:flutter/src/rendering/mouse_tracker.dart:417:29)
           #15     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:633:13)
           #16     MouseTracker._handleDeviceUpdateMouseEvents (package:flutter/src/rendering/mouse_tracker.dart:414:21)
           #17     MouseTracker._handleDeviceUpdate (package:flutter/src/rendering/mouse_tracker.dart:282:5)
           #18     MouseTracker.updateWithEvent.<anonymous closure>.<anonymous closure> (package:flutter/src/rendering/mouse_tracker.dart:351:9)
           #19     MouseTracker._deviceUpdatePhase (package:flutter/src/rendering/mouse_tracker.dart:210:9)
           #20     MouseTracker.updateWithEvent.<anonymous closure> (package:flutter/src/rendering/mouse_tracker.dart:328:7)
           #21     MouseTracker._monitorMouseConnection (package:flutter/src/rendering/mouse_tracker.dart:193:9)
           #22     MouseTracker.updateWithEvent (package:flutter/src/rendering/mouse_tracker.dart:327:5)
           #23     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:432:20)
           #24     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:413:7)
           #25     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:376:5)
           #26     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:323:7)
           #27     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:292:9)
           #28     _invoke1 (dart:ui/hooks.dart:328:13)
           #29     PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:410:7)
           #30     _dispatchPointerDataPacket (dart:ui/hooks.dart:262:31)

我已经尝试过以下方法但未解决:

  • 添加WidgetsBinding.instance.postFrameCall
  • 使用mounted属性
  • 添加延迟Future

解决方案

这个异常的核心原因是:页面导航后,原Scaffold或Drawer对应的OverlayEntry已被销毁,但鼠标移出的回调仍在尝试调用closeDrawer,导致操作已被销毁的Overlay对象。可以通过以下方式解决:

1. 直接控制DrawerController并验证状态

给Drawer添加单独的GlobalKey,直接通过DrawerController控制关闭,并在操作前验证状态是否活跃:

final GlobalKey<DrawerControllerState> _drawerControllerKey = GlobalKey();

// 自定义Scaffold的build方法中:
Drawer(
  key: _drawerControllerKey,
  child: /* 你的Drawer内容 */,
)

// 鼠标移出回调修改为:
onExit: (_) {
  final drawerState = _drawerControllerKey.currentState;
  if (drawerState != null && drawerState.mounted) {
    drawerState.close();
  }
},

2. 双重验证组件状态

在调用closeDrawer前,同时验证自定义Scaffold的mounted状态和ScaffoldState的有效性:

onExit: (_) {
  if (mounted && _scaffoldKey.currentState != null && _scaffoldKey.currentState!.mounted) {
    _scaffoldKey.currentState?.closeDrawer();
  }
},

3. 将Drawer抽离到全局Overlay(彻底解决持久化问题)

如果要实现真正的Drawer持久化,建议将Drawer从Scaffold中抽离,手动添加到全局Overlay中,这样页面导航时Drawer不会随页面销毁:

// 在全局状态类或根组件中管理Drawer的OverlayEntry
OverlayEntry? _drawerOverlayEntry;

void showPersistentDrawer(BuildContext context) {
  if (_drawerOverlayEntry == null) {
    _drawerOverlayEntry = OverlayEntry(
      builder: (context) => MouseRegion(
        onExit: (_) => hidePersistentDrawer(),
        child: Drawer(/* 你的Drawer内容 */),
      ),
    );
    Overlay.of(context)?.insert(_drawerOverlayEntry!);
  }
}

void hidePersistentDrawer() {
  _drawerOverlayEntry?.remove();
  _drawerOverlayEntry = null;
}

这种方式完全脱离Scaffold的管理,从根本上避免了页面导航导致的组件销毁问题,更符合"持久化Drawer"的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 02:21:03