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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 03:32:09