Flutter中TextFormField输入/删字符后键盘自动收起问题求助
解决TextFormField输入后键盘自动收起的问题
核心问题分析
你的代码导致键盘意外收起的原因有三个:
- Dismissible使用
UniqueKey():每次调用setState()后,UniqueKey()会生成全新的Key,导致Dismissible及其子组件(包括TextFormField)被强制重建,输入框失去焦点,键盘随之收起。 - 直接操作父组件传入的列表:在
build()方法中重复赋值players = widget.playerNames,没有在State内部维护独立的状态副本,导致状态更新逻辑混乱。 - 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
相关产品推荐
相关产品推荐

