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

Flutter中TextFormField输入/删字符后键盘自动收起问题求助

解决TextFormField输入后键盘自动收起的问题

核心问题分析

你的代码导致键盘意外收起的原因有三个:

  1. Dismissible使用UniqueKey():每次调用setState()后,UniqueKey()会生成全新的Key,导致Dismissible及其子组件(包括TextFormField)被强制重建,输入框失去焦点,键盘随之收起。
  2. 直接操作父组件传入的列表:在build()方法中重复赋值players = widget.playerNames,没有在State内部维护独立的状态副本,导致状态更新逻辑混乱。
  3. TextFormField依赖initialValue:initialValue仅在组件初始化时生效,组件重建时会重置输入框内容并丢失焦点。

修复步骤及完整代码

1. 维护State内的独立状态

在State类中创建专属的玩家列表和输入控制器列表,避免直接修改父组件传入的参数。

2. 使用稳定的Key

用ValueKey(_players[index])替代UniqueKey(),保证组件仅在玩家名称变化时才重建,避免不必要的组件刷新。

3. 改用TextEditingController

通过控制器管理输入框内容,彻底避免组件重建时的输入状态丢失。

修复后的完整代码:

import 'package:flutter/material.dart';
import 'package:r6stats/scanner_result.dart';

class PlayerEditor extends StatefulWidget {
  const PlayerEditor({super.key, required this.playerNames});

  final List<String> playerNames;

  @override
  State<PlayerEditor> createState() => _PlayerEditorState();
}

class _PlayerEditorState extends State<PlayerEditor> {
  late List<String> _players;
  late List<TextEditingController> _controllers;

  @override
  void initState() {
    super.initState();
    // 创建父组件列表的副本,避免直接修改外部状态
    _players = List.from(widget.playerNames);
    // 为每个玩家初始化输入控制器
    _controllers = _players.map((name) => TextEditingController(text: name)).toList();
  }

  @override
  void dispose() {
    // 释放所有控制器资源,避免内存泄漏
    for (var controller in _controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Edit Players"),
        actions: [
          IconButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => ScannerResult(playerNames: _players),
                  ),
                );
              },
              icon: const Icon(Icons.check))
        ],
      ),
      body: ListView.builder(
        itemCount: _players.length,
        itemBuilder: (context, index) => Padding(
          padding: const EdgeInsets.all(8),
          child: Dismissible(
            // 使用稳定的Key,仅当玩家名称变化时才重建组件
            key: ValueKey(_players[index]),
            direction: DismissDirection.startToEnd,
            onDismissed: (direction) {
              final playerName = _players[index];
              final controller = _controllers[index];
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                content: Text("${playerName} dismissed"),
                action: SnackBarAction(
                  label: 'UNDO',
                  onPressed: () {
                    setState(() {
                      _players.insert(index, playerName);
                      _controllers.insert(index, controller);
                    });
                  },
                ),
              ));
              setState(() {
                _players.removeAt(index);
                _controllers.removeAt(index);
              });
            },
            background: Container(
              color: Colors.red,
              alignment: Alignment.centerLeft,
              padding: const EdgeInsets.only(left: 20.0),
              child: const Icon(Icons.delete, color: Colors.white),
            ),
            child: ListTile(
              tileColor: const Color.fromRGBO(194, 194, 194, 100),
              shape: const RoundedRectangleBorder(
                  side: BorderSide(color: Colors.black, width: 1)),
              title: TextFormField(
                // 使用控制器管理输入内容,避免重建时丢失状态
                controller: _controllers[index],
                onChanged: (newValue) {
                  setState(() {
                    _players[index] = newValue;
                  });
                },
                style: Theme.of(context)
                    .textTheme
                    .titleMedium!
                    .copyWith(color: Colors.black),
                decoration: const InputDecoration(
                  border: InputBorder.none,
                  focusedBorder: InputBorder.none,
                  hintText: 'Enter player name',
                ),
              ),
              titleTextStyle: Theme.of(context)
                  .textTheme
                  .titleMedium!
                  .copyWith(color: Colors.black),
            ),
          ),
        ),
      ),
    );
  }
}

额外说明

  • 不要在build()方法中创建状态变量,否则每次组件重建都会重置状态。
  • 如果存在重名玩家,可以将Key改为ValueKey('${index}_${_players[index]}'),保证Key的唯一性。

内容的提问来源于stack exchange,提问作者Ziad Ghanem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:44:52