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

Flutter中bottom navigation bar页面切换时如何弹出确认提示对话框?

解决方案

要实现底部导航切换时检测未提交表单并提示的功能,你需要同时处理底部导航切换逻辑和系统返回按钮两种场景,具体可以通过以下步骤实现:

1. 跟踪表单的未保存状态

在表单页面中,监听输入字段的变化,维护一个布尔值标记是否存在未提交的内容,并通过回调将状态同步给父组件(包含底部导航的页面)。

2. 拦截底部导航的切换事件

在父组件的底部导航点击回调中,判断当前是否处于表单页面、是否要切换到其他页面,同时检查表单的未保存状态,满足条件则弹出确认对话框。

3. 配合WillPopScope处理系统返回

保留WillPopScope来处理系统返回按钮的场景,确保两种退出路径都能触发提示。

完整代码示例

父组件(包含底部导航)

class BottomNavContainer extends StatefulWidget {
  @override
  _BottomNavContainerState createState() => _BottomNavContainerState();
}

class _BottomNavContainerState extends State<BottomNavContainer> {
  int _currentTabIndex = 0;
  bool _hasUnsavedFormChanges = false;

  final List<Widget> _pages = [
    HomePage(),
    // 传递回调给表单页面,同步未保存状态
    FormPage(
      onUnsavedChangesUpdated: (hasChanges) {
        setState(() {
          _hasUnsavedFormChanges = hasChanges;
        });
      },
    ),
    ProfilePage(),
  ];

  void _onTabChanged(int targetIndex) {
    // 触发条件:当前在表单页(索引1)、要切换到其他页、存在未保存内容
    if (_currentTabIndex == 1 && targetIndex != 1 && _hasUnsavedFormChanges) {
      showDialog(
        context: context,
        builder: (ctx) => AlertDialog(
          title: Text('提示'),
          content: Text('您已填写信息,离开此页面将丢弃内容'),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(ctx),
              child: Text('取消'),
            ),
            TextButton(
              onPressed: () {
                Navigator.pop(ctx);
                setState(() {
                  _currentTabIndex = targetIndex;
                  // 切换后重置状态(也可让表单页面自行处理)
                  _hasUnsavedFormChanges = false;
                });
              },
              child: Text('确认离开'),
            ),
          ],
        ),
      );
    } else {
      setState(() {
        _currentTabIndex = targetIndex;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentTabIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentTabIndex,
        onTap: _onTabChanged,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.edit_note), label: '表单'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }
}

表单页面

class FormPage extends StatefulWidget {
  final void Function(bool) onUnsavedChangesUpdated;

  const FormPage({required this.onUnsavedChangesUpdated, Key? key}) : super(key: key);

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

class _FormPageState extends State<FormPage> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  bool _hasUnsavedChanges = false;

  @override
  void initState() {
    super.initState();
    // 监听输入框变化,同步未保存状态
    _nameController.addListener(_checkForUnsavedChanges);
    _emailController.addListener(_checkForUnsavedChanges);
  }

  void _checkForUnsavedChanges() {
    final currentHasChanges = _nameController.text.isNotEmpty || _emailController.text.isNotEmpty;
    if (currentHasChanges != _hasUnsavedChanges) {
      setState(() {
        _hasUnsavedChanges = currentHasChanges;
      });
      widget.onUnsavedChangesUpdated(currentHasChanges);
    }
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      // 执行表单提交逻辑
      setState(() {
        _hasUnsavedChanges = false;
      });
      widget.onUnsavedChangesUpdated(false);
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('提交成功')));
    }
  }

  // 处理系统返回按钮
  Future<bool> _handleBackPress() async {
    if (!_hasUnsavedChanges) return true;

    final shouldLeave = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('提示'),
        content: const Text('您已填写信息,离开此页面将丢弃内容'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')),
          TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确认离开')),
        ],
      ),
    );

    return shouldLeave ?? false;
  }

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: _handleBackPress,
      child: Scaffold(
        appBar: AppBar(title: const Text('填写表单')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Form(
            key: _formKey,
            child: Column(
              children: [
                TextFormField(
                  controller: _nameController,
                  decoration: const InputDecoration(labelText: '姓名'),
                  validator: (val) => val?.isEmpty ?? true ? '请输入姓名' : null,
                ),
                TextFormField(
                  controller: _emailController,
                  decoration: const InputDecoration(labelText: '邮箱'),
                  validator: (val) => val?.isEmpty ?? true ? '请输入邮箱' : null,
                ),
                const SizedBox(height: 24),
                ElevatedButton(
                  onPressed: _submitForm,
                  child: const Text('提交表单'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

扩展场景:复杂状态管理

如果你的项目已经使用状态管理工具(如Provider、Riverpod),可以将表单的未保存状态抽离到全局状态中,避免父子组件的回调传递,让底部导航的逻辑更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 13:31:12