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

Flutter路由切换时如何执行函数以关闭自定义环形菜单组件

问题根因

你跳转页面时,环形菜单的_isOpen状态仍为开启,动画控制器也没有重置到初始值,所以返回页面时会直接复用跳转前的状态,就出现了菜单保持展开、子项间距变大的问题。

实现方案

方案1:组件内部拦截子菜单点击事件(最直接)

你当前代码里子菜单外层的GestureDetector没有配置点击逻辑,我们可以在这里先拦截点击,先执行关闭动画,再触发子项的原有跳转操作:

步骤1:修改_renderSubMenu方法,给GestureDetector加onTap逻辑

List<Widget> _renderSubMenu(List<Widget> children, double distance) {
  List<Widget> _subMenu = [];
  double _angleRatio = 360 / children.length;

  children.forEachIndexed((child, index) {
    _subMenu.add(
      Transform.translate(
          offset: Offset.fromDirection(
              convertDegreesToRadians(index * _angleRatio), distance),
          child: GestureDetector(
            onTap: () async {
              // 先执行关闭逻辑
              close();
              // 等待关闭动画执行完成再跳转,时长和你定义的最长动画时长一致,这里是500ms
              await Future.delayed(const Duration(milliseconds: 500));
            },
            child: child)),
    );
  });

  return _subMenu;
}

如果你的子菜单项本身自带点击事件(比如本身是IconButton),可以调整组件参数,避免点击事件冲突:

步骤(可选):调整组件入参规范子项配置

首先定义子项数据类:

class CircularMenuItem {
  final Widget icon;
  final VoidCallback onTap;

  CircularMenuItem({required this.icon, required this.onTap});
}

然后修改CircularMenu的参数:

class CircularMenu extends StatefulWidget {
  final Widget menuIcon;
  // 把children改成接收CircularMenuItem列表
  final List<CircularMenuItem>? menuItems;
  CircularMenu({Key? key, this.menuItems, required this.menuIcon})
      : super(key: key);

  @override
  _CircularMenuState createState() => _CircularMenuState();
}

最后修改_renderSubMenu的点击逻辑:

List<Widget> _renderSubMenu(List<CircularMenuItem> items, double distance) {
  List<Widget> _subMenu = [];
  double _angleRatio = 360 / items.length;

  items.forEachIndexed((item, index) {
    _subMenu.add(
      Transform.translate(
          offset: Offset.fromDirection(
              convertDegreesToRadians(index * _angleRatio), distance),
          child: GestureDetector(
            onTap: () async {
              close();
              await Future.delayed(const Duration(milliseconds: 500));
              // 执行子项原本的跳转逻辑
              item.onTap();
            },
            child: item.icon)),
    );
  });

  return _subMenu;
}

方案2:页面生命周期监听兜底

如果需要覆盖用户未点击子项直接退出页面的场景,可以在组件内监听路由生命周期,页面被移除时自动关闭菜单:

步骤1:让State类实现WidgetsBindingObserver

class _CircularMenuState extends State<CircularMenu>
    with TickerProviderStateMixin, WidgetsBindingObserver {

步骤2:注册/移除生命周期监听

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addObserver(this);
  // ... 原有初始化逻辑保留
}

@override
void dispose() {
  WidgetsBinding.instance.removeObserver(this);
  _animationController.dispose();
  _subMenuPositionController.dispose();
  super.dispose();
}

步骤3:重写路由监听方法

// 路由弹出时自动关闭菜单
@override
Future<bool> didPopRoute() async {
  if (_isOpen) {
    close();
  }
  return super.didPopRoute();
}

方案3:GlobalKey暴露close方法(父组件手动控制)

如果需要父组件灵活控制菜单关闭,可以把State改为公开类,父组件通过GlobalKey调用关闭方法:

步骤1:把State类改为公开

// 去掉类名前的下划线
class CircularMenuState extends State<CircularMenu>
    with TickerProviderStateMixin {
  // ... 原有逻辑全部保留
}

步骤2:父组件使用时传入GlobalKey

// 父组件内定义key
final GlobalKey<CircularMenuState> menuKey = GlobalKey();

// 调用组件时传入key
CircularMenu(
  key: menuKey,
  menuIcon: Icon(Icons.menu),
  // ... 其他参数
)

// 跳转前手动调用关闭
onTap: () async {
  menuKey.currentState?.close();
  await Future.delayed(const Duration(milliseconds: 500));
  // 执行跳转逻辑
  Navigator.of(context).push(...);
}

内容的提问来源于stack exchange,提问作者João Pedro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 19:45:03