Flutter切换页面后键盘未关闭且输入框可后台更新问题
Flutter页面切换时键盘异常与状态残留问题解决
问题现象
- 页面内的TextFormField点击时键盘正常弹出
- 通过Drawer切换页面后,键盘先关闭,新页面加载完成后又自动打开
- 在新页面输入内容时,后台会同步更新原页面的输入框值,返回原页面能看到输入结果
- 预期行为:切换页面时原页面/Widget应被销毁,其他页面无法更新其输入框内容
问题根源
核心原因是首页使用了IndexedStack组件管理页面切换:
- IndexedStack会保留所有子Widget的状态,未显示的页面不会被销毁,只是隐藏
- 原页面的TextFormField仍处于活跃状态,焦点逻辑混乱导致键盘异常
- 原页面的状态变量(如
buildHomes)未被重置,输入事件会继续更新这些变量
解决方案
方案1:替换IndexedStack为条件渲染(推荐)
直接根据当前索引返回对应页面,未选中的页面会被销毁,彻底解决状态残留问题:
修改selectedTabContent()方法:
Widget selectedTabContent() { switch(_drawerNavIndex) { case 0: return const TribeMailScreen(); case 1: return const TribeHomeScreen(); case 2: return const TribeAdvisorScreen(); case 3: return const ConstructionScreen(); default: return const ConstructionScreen(); } }
方案2:保留IndexedStack时手动处理焦点与状态
如果需要保留IndexedStack(比如追求页面切换流畅度),需手动管理焦点和状态:
- 切换页面时关闭键盘:在
setSelectedTab方法中添加焦点释放逻辑
void setSelectedTab(index) { if (_drawerNavIndex != index) { // 主动关闭键盘并释放当前焦点 FocusManager.instance.primaryFocus?.unfocus(); setState(() { _drawerNavIndex = index; }); } }
- 为TextFormField绑定独立FocusNode:在包含输入框的页面(如ConstructionScreen)中创建并管理焦点节点
class ConstructionScreen extends StatefulWidget { const ConstructionScreen({Key? key}) : super(key: key); @override State<ConstructionScreen> createState() => _ConstructionScreenState(); } class _ConstructionScreenState extends State<ConstructionScreen> { final FocusNode _homesFocusNode = FocusNode(); int buildHomes = 0; @override void dispose() { // 页面销毁时释放焦点节点 _homesFocusNode.dispose(); // 重置输入状态 buildHomes = 0; super.dispose(); } @override Widget build(BuildContext context) { return Form( key: _constructionFormKey, child: Column(children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox( width: 100, child: Text( 'Homes', style: regularBoldText, ), ), SizedBox( width: 75, child: Text( '${widget.tribe.homes} (${calculatePercent(widget.tribe.land, widget.tribe.homes)}%)', style: regularText, ), ), SizedBox( height: 18, width: MediaQuery.of(context).size.width / 3, child: TextFormField( focusNode: _homesFocusNode, // 绑定焦点节点 autovalidateMode: AutovalidateMode.onUserInteraction, onChanged: (String? newValue) { if (newValue != null && isNumber(newValue)) { setState(() { buildHomes = int.parse(newValue); }); } else if (newValue == null || newValue.isEmpty) { setState(() { buildHomes = 0; }); } }, inputFormatters: <TextInputFormatter>[ FilteringTextInputFormatter.digitsOnly ], style: const TextStyle(fontSize: 10), decoration: const InputDecoration( border: OutlineInputBorder()), keyboardType: TextInputType.number, ), ), ], ), ]), ); } }
附:原问题相关代码
表单字段代码
Form(key: _constructionFormKey, child: Column(children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox( width: 100, child: Text( 'Homes', style: regularBoldText, ), ), SizedBox( width: 75, child: Text( '${widget.tribe.homes} (${calculatePercent(widget.tribe.land, widget.tribe.homes)}%)', style: regularText, ), ), SizedBox( height: 18, width: MediaQuery.of(context).size.width / 3, child: TextFormField( autovalidateMode: AutovalidateMode.onUserInteraction, onChanged: (String? newValue) { if (newValue != null && isNumber(newValue)) { setState(() { buildHomes = int.parse(newValue); }); } else if (newValue == null || newValue.isEmpty) { setState(() { buildHomes = 0; }); } }, inputFormatters: <TextInputFormatter>[ FilteringTextInputFormatter.digitsOnly ], style: const TextStyle(fontSize: 10), decoration: const InputDecoration( border: OutlineInputBorder()), keyboardType: TextInputType.number, ), ), ], ), ]), );
首页导航代码
class HomeScreen extends StatefulWidget { const HomeScreen({Key? key}) : super(key: key); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { int _drawerNavIndex = 3; void setSelectedTab(index) { if (_drawerNavIndex != index) { setState(() { _drawerNavIndex = index; }); } } Widget selectedTabContent() { List<Widget> pages = [ const TribeMailScreen(), const TribeHomeScreen(), const TribeAdvisorScreen(), const ConstructionScreen() ]; return IndexedStack( index: _drawerNavIndex, children: pages, ); } @override Widget build(BuildContext context) { TribeSummary tribe = Provider.of<TribeSummary>(context, listen: true); if (tribe.uid == 'placeHolderTribe') { return Scaffold( appBar: AppBar( title: const Text('Orkfia'), ), body: const StartTribeWidget(), ); } else if (tribe.uid == 'placeHolderErrorTribe') { return const Center( child: Text('Unable to retrieve tribe data'), ); } return Scaffold( appBar: const AppBarContent(), drawer: DrawerContentWidget( setSelectedTab: setSelectedTab, selectedTabIndex: _drawerNavIndex, ), body: selectedTabContent(), bottomNavigationBar: SizedBox( height: 50, child: Container( color: Colors.red[100], child: const Center(child: Text('Reserved space')), )), ); } }
内容的提问来源于stack exchange,提问作者Trax
相关产品推荐
相关产品推荐

