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

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(比如追求页面切换流畅度),需手动管理焦点和状态:

  1. 切换页面时关闭键盘:在setSelectedTab方法中添加焦点释放逻辑
void setSelectedTab(index) {
  if (_drawerNavIndex != index) {
    // 主动关闭键盘并释放当前焦点
    FocusManager.instance.primaryFocus?.unfocus();
    setState(() {
      _drawerNavIndex = index;
    });
  }
}
  1. 为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 12:40:28