Flutter:文本框为空时限制点击外部关闭showModalBottomSheet
解决方案
要实现输入框为空时禁止点击弹窗外部关闭的需求,核心是拦截弹窗的关闭事件,可通过WillPopScope组件结合输入框状态判断来实现,具体步骤如下:
1. 调整showModalBottomSheet参数
保持enableDrag: false(禁用下滑关闭),将isDismissible设为true,后续通过WillPopScope实际控制关闭权限:
showModalBottomSheet( clipBehavior: Clip.hardEdge, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), backgroundColor: Colors.transparent, enableDrag: false, isDismissible: true, // 先开启基础关闭权限,后续通过拦截逻辑控制 isScrollControlled: true, builder: (BuildContext context) => BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: const DraggablePage(), ), );
2. 用WillPopScope包裹弹窗内容
在DraggablePage的Scaffold外层添加WillPopScope,重写onWillPop方法,根据输入框内容是否为空决定是否允许关闭:
@override Widget build(BuildContext context) { return DraggableScrollableSheet( maxChildSize: 0.8, minChildSize: 0.2, controller: draggableScrollableController, builder: (BuildContext context, ScrollController scrollController) => WillPopScope( onWillPop: () async { // 判断输入框是否为空 if (_controller.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('输入框不能为空,请填写后再关闭')), ); return false; // 返回false阻止弹窗关闭 } return true; // 返回true允许弹窗关闭 }, child: Scaffold( // 原Scaffold内容 ), ), ); }
3. 优化表单验证逻辑
保留原有表单验证,确保点击返回按钮时仍需验证输入合法性,同时简化状态变量(无需额外维护isValidField)。
修改后的完整代码
import 'dart:ui'; import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp( home: HomePage(), )); } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Draggable scrollable sheet example'), backgroundColor: Colors.teal, ), body: Center( child: ElevatedButton( child: const Text('打开底部弹窗'), onPressed: () { showModalBottomSheet( clipBehavior: Clip.hardEdge, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), backgroundColor: Colors.transparent, enableDrag: false, isDismissible: true, isScrollControlled: true, builder: (BuildContext context) => BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: const DraggablePage(), ), ); }), ), ); } } class DraggablePage extends StatefulWidget { const DraggablePage({super.key}); @override State<DraggablePage> createState() => _DraggablePageState(); } class _DraggablePageState extends State<DraggablePage> { final DraggableScrollableController draggableScrollableController = DraggableScrollableController(); final TextEditingController _controller = TextEditingController(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return DraggableScrollableSheet( maxChildSize: 0.8, minChildSize: 0.2, controller: draggableScrollableController, builder: (BuildContext context, ScrollController scrollController) => WillPopScope( onWillPop: () async { if (_controller.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('输入框不能为空,请填写后再关闭')), ); return false; } return true; }, child: Scaffold( appBar: AppBar( title: const Text('Draggable scrollable sheet example'), backgroundColor: Colors.teal, ), body: Center( child: SingleChildScrollView( child: Form( key: _formKey, child: Column( children: <Widget>[ TextFormField( controller: _controller, onChanged: (String value) { setState(() {}); }, validator: (String? value) { if (value?.isEmpty ?? true) { return '此字段不能为空'; } return null; }, decoration: const InputDecoration(hintText: '在此输入内容'), ), ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { Navigator.pop(context); } }, child: const Text('返回'), ), ], ), ), ), ), ), ), ); } }
原理说明
- WillPopScope:拦截弹窗的返回事件(包括点击外部触发的关闭操作),通过返回值控制是否允许关闭。
- 实时状态判断:在
onWillPop中直接检查输入框内容,为空时提示用户并阻止关闭,非空则允许关闭。 - 保留表单验证:点击返回按钮时仍需通过表单验证,确保输入合法性。
内容的提问来源于stack exchange,提问作者Gowtham
相关产品推荐
相关产品推荐

