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

Flutter问题:拖拽关闭BottomSheetModal时PopScope失效

解决Flutter模态框拖拽关闭时不触发确认对话框的问题

要实现表单有输入内容时,拖拽关闭模态框也弹出放弃确认的逻辑,需要从两个方面调整代码:拦截模态框的所有Pop动作(包括拖拽)、跟踪表单输入状态。

关键修改点

  1. 添加输入状态跟踪:用TextEditingController监听表单输入,判断是否有内容。
  2. 全局拦截Pop动作:将PopScope作为模态框的根组件,覆盖整个模态框区域,这样拖拽、点击返回键等触发的Pop都会被拦截。
  3. 条件触发确认对话框:在onPopInvoked中根据输入状态决定直接关闭还是弹出确认框。

修改后的完整代码

import 'package:flutter/material.dart';

void main() => runApp(const NavigatorPopHandlerApp());

class NavigatorPopHandlerApp extends StatelessWidget {
  const NavigatorPopHandlerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/home',
      routes: <String, WidgetBuilder>{
        '/home': (BuildContext context) => const _HomePage(),
      },
    );
  }
}

class _HomePage extends StatefulWidget {
  const _HomePage();

  @override
  State<_HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<_HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Page One'),
            TextButton(
              onPressed: () {
                showModalBottomSheet<void>(
                  context: context,
                  enableDrag: true,
                  builder: (BuildContext context) {
                    return const _FormModal();
                  },
                );
              },
              child: const Text('Show Bottom Sheet'),
            ),
          ],
        ),
      ),
    );
  }
}

class _FormModal extends StatefulWidget {
  const _FormModal();

  @override
  State<_FormModal> createState() => _FormModalState();
}

class _FormModalState extends State<_FormModal> {
  final TextEditingController _inputController = TextEditingController();

  void _showDiscardDialog() {
    showDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('确定要离开吗?'),
          content: const Text(
            '离开后输入的内容将会丢失,确定要放弃吗?',
          ),
          actions: <Widget>[
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('取消'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('离开'),
              onPressed: () {
                Navigator.pop(context);
                Navigator.pop(context);
              },
            ),
          ],
        );
      },
    );
  }

  @override
  void dispose() {
    _inputController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvoked: (bool didPop) {
        if (didPop) return;
        
        if (_inputController.text.isNotEmpty) {
          _showDiscardDialog();
        } else {
          Navigator.pop(context);
        }
      },
      child: Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Container(color: Colors.black54, height: 4, width: 48),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text('用户信息表单'),
                IconButton(
                  onPressed: () {
                    if (_inputController.text.isNotEmpty) {
                      _showDiscardDialog();
                    } else {
                      Navigator.pop(context);
                    }
                  },
                  icon: const Icon(Icons.close),
                ),
              ],
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _inputController,
              decoration: const InputDecoration(
                hintText: '请输入内容',
                border: OutlineInputBorder(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

代码解释

  • TextEditingController:监听输入框内容变化,作为是否触发确认对话框的判断依据。
  • PopScope根组件:覆盖整个模态框区域,确保拖拽、返回键、代码调用Navigator.pop等所有Pop动作都会进入回调处理。
  • 条件判断逻辑:仅当输入框有内容时弹出确认对话框,无内容时直接关闭,避免不必要的交互干扰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 23:48:12