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

Flutter:VoidCallback在OnPressed生效但OnTap无效的问题

PopupMenuItem onTap无法触发showModalBottomSheet的问题分析

问题场景

我定义了一个PopupMenu组件:

class PopupMenu extends StatelessWidget {
final VoidCallback editTaskCallback;
const PopupMenu({
    required this.editTaskCallback,
    super.key,
  });
 @override
  Widget build(BuildContext context) {
    return PopupMenuButton(
        itemBuilder:(context) => [
                PopupMenuItem(
                  onTap: null,
                  child: TextButton.icon(
                    onPressed: editTaskCallback,
                    icon: const Icon(Icons.edit),
                    label: const Text('Edit'),
                  ),
                ),
             ],
    );
  }
}

调用时传入回调:

PopupMenu(
  editTaskCallback: () {_editTask(context)},
),

其中_editTask方法用于弹出底部sheet:

void _editTask(BuildContext context) {
  showModalBottomSheet(
    isScrollControlled: true,
    context: context,
    builder: (context) => SingleChildScrollView(
      child: Container(
        padding:
            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: EditTaskBottom(
          oldTask: task,
        ),
      ),
    ),
  );
}

遇到的问题:将editTaskCallback赋值给TextButton的onPressed时,showModalBottomSheet能正常弹出;但赋值给PopupMenuItem的onTap时,回调里的print语句能执行,却无法弹出底部sheet。

原因解析

  • PopupMenuItem的onTap是在菜单关闭流程中触发的:点击PopupMenuItem后,Flutter会先执行onTap,紧接着调用Navigator.pop关闭菜单。此时showModalBottomSheet依赖的context已经因为菜单关闭而失效,导致无法正确弹出。
  • TextButton的onPressed是在菜单关闭前触发的:点击TextButton时,菜单还处于打开状态,对应的context依然有效,所以showModalBottomSheet能正常工作。

解决方法

方法1:延迟执行回调

通过延迟执行showModalBottomSheet,等待菜单完全关闭后再弹出底部sheet:

void _editTask(BuildContext context) {
  Future.delayed(const Duration(milliseconds: 100), () {
    showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      builder: (context) => SingleChildScrollView(
        child: Container(
          padding:
              EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
          child: EditTaskBottom(
            oldTask: task,
          ),
        ),
      ),
    );
  });
}

方法2:使用根导航上下文

获取根导航的context,避免菜单关闭带来的上下文失效问题:

void _editTask(BuildContext context) {
  showModalBottomSheet(
    isScrollControlled: true,
    context: Navigator.of(context, rootNavigator: true),
    builder: (context) => SingleChildScrollView(
      child: Container(
        padding:
            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: EditTaskBottom(
          oldTask: task,
        ),
      ),
    ),
  );
}

方法3:使用PopupMenuButton的onSelected(推荐)

重构组件,利用PopupMenuButton的onSelected回调,该回调会在菜单关闭后触发,无需担心上下文问题:

class PopupMenu extends StatelessWidget {
  final VoidCallback editTaskCallback;
  const PopupMenu({
    required this.editTaskCallback,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton(
      onSelected: (value) {
        if (value == 'edit') {
          editTaskCallback();
        }
      },
      itemBuilder:(context) => [
        const PopupMenuItem(
          value: 'edit',
          child: Row(
            children: [
              Icon(Icons.edit),
              SizedBox(width: 8),
              Text('Edit'),
            ],
          ),
        ),
      ],
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 11:22:44