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
相关产品推荐
相关产品推荐

