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

Flutter:实现BottomSheet内TextField输入时实时启用/禁用TextButton

Flutter BottomSheet 中文本输入实时更新按钮状态问题解决

问题描述

我需要在BottomSheet中放置一个TextField和一个TextButton,要求仅当TextField内有文本时才启用TextButton。但目前仅在点击屏幕或按下回车键时,按钮的启用/禁用状态才会变化,无法实时响应输入内容的改变。相同的实现逻辑在BottomSheet外可正常运行,完整代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyBottomSheet(),
    );
  }
}
class MyBottomSheet extends StatefulWidget {
  @override
  _MyBottomSheetState createState() => _MyBottomSheetState();
}

class _MyBottomSheetState extends State<MyBottomSheet> {
  late TextEditingController _textController;
  bool _isButtonDisabled = true;

  @override
  void initState() {
    super.initState();
    _textController = TextEditingController();
    _textController.addListener(_onTextChanged);
  }

  void _onTextChanged() {
    setState(() {
      _isButtonDisabled = _textController.text.isEmpty;
    });
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }
  void _showBottomSheet() {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext context) {
        return Padding(
          padding: EdgeInsets.only(
            bottom: MediaQuery.of(context).viewInsets.bottom
          ),
          child: Container(
            height: 300,
            width: double.infinity,
            child: Column(
              children: [
                TextField(
                  controller: _textController,
                  autofocus: true,
                  decoration: const InputDecoration(
                    contentPadding: EdgeInsets.symmetric(horizontal: 15),
                    hintText: 'Enter name...',
                  ),
                ),
                TextButton(
                  onPressed: _isButtonDisabled ? null : () {
                    
                  },
                  child: const Text('Save'),
                )
              ],
            ),
          ),
        );
      }
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TEST'),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked ,
        floatingActionButton: FloatingActionButton(
          shape: CircleBorder(),
          onPressed: (_showBottomSheet),
          child: Icon(Icons.add_rounded),
        ),
        bottomNavigationBar: BottomAppBar(
          height: 60,
          shape: CircularNotchedRectangle(),
          notchMargin: 8.0,
          clipBehavior: Clip.antiAlias,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              IconButton(
                onPressed: () {},
                icon: Icon(Icons.more_horiz_rounded),
              ),
              IconButton(
                onPressed: () {},
                icon: Icon(Icons.menu_rounded)
              )
            ],
          ),
        ),
    );
  }

  void _onSubmit() {
    // Handle form submission here
  }
}

问题原因

showModalBottomSheet会创建一个独立的路由上下文,外层页面的setState()只能触发外层Scaffold的UI重建,无法通知到BottomSheet内部的组件更新。虽然TextEditingController的监听器确实在实时触发状态变更,但BottomSheet的内容并没有跟着重新构建,导致按钮状态无法实时刷新。

解决方案

方法一:使用StatefulBuilder包裹BottomSheet内容

通过StatefulBuilder获取BottomSheet内部的状态更新方法,替代外层setState()来触发BottomSheet内的UI重建:

void _showBottomSheet() {
  showModalBottomSheet(
    context: context,
    builder: (BuildContext context) {
      return StatefulBuilder(
        builder: (context, setState) {
          return Padding(
            padding: EdgeInsets.only(
              bottom: MediaQuery.of(context).viewInsets.bottom
            ),
            child: Container(
              height: 300,
              width: double.infinity,
              child: Column(
                children: [
                  TextField(
                    controller: _textController,
                    autofocus: true,
                    decoration: const InputDecoration(
                      contentPadding: EdgeInsets.symmetric(horizontal: 15),
                      hintText: 'Enter name...',
                    ),
                    onChanged: (text) {
                      setState(() {
                        _isButtonDisabled = text.isEmpty;
                      });
                    },
                  ),
                  TextButton(
                    onPressed: _isButtonDisabled ? null : () {
                      // 处理保存逻辑
                    },
                    child: const Text('Save'),
                  )
                ],
              ),
            ),
          );
        },
      );
    }
  );
}

方法二:将BottomSheet内容抽为独立StatefulWidget

把BottomSheet的内容单独封装成StatefulWidget,让它自己管理文本控制器和按钮状态,状态变更直接触发自身UI重建:

// 新增独立组件
class BottomSheetContent extends StatefulWidget {
  const BottomSheetContent({super.key});

  @override
  State<BottomSheetContent> createState() => _BottomSheetContentState();
}

class _BottomSheetContentState extends State<BottomSheetContent> {
  late TextEditingController _textController;
  bool _isButtonDisabled = true;

  @override
  void initState() {
    super.initState();
    _textController = TextEditingController();
    _textController.addListener(_onTextChanged);
  }

  void _onTextChanged() {
    setState(() {
      _isButtonDisabled = _textController.text.isEmpty;
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(
        bottom: MediaQuery.of(context).viewInsets.bottom
      ),
      child: Container(
        height: 300,
        width: double.infinity,
        child: Column(
          children: [
            TextField(
              controller: _textController,
              autofocus: true,
              decoration: const InputDecoration(
                contentPadding: EdgeInsets.symmetric(horizontal: 15),
                hintText: 'Enter name...',
              ),
            ),
            TextButton(
              onPressed: _isButtonDisabled ? null : () {
                // 处理保存逻辑
                Navigator.pop(context);
              },
              child: const Text('Save'),
            )
          ],
        ),
      ),
    );
  }
}

// 修改_showBottomSheet方法
void _showBottomSheet() {
  showModalBottomSheet(
    context: context,
    builder: (BuildContext context) {
      return const BottomSheetContent();
    }
  );
}

方案对比

  • 方法一适合简单场景,无需新增组件,代码改动小;
  • 方法二更适合复杂BottomSheet内容,状态管理更清晰,代码解耦性更强。

内容的提问来源于stack exchange,提问作者Tiago Fernandes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 02:38:15