Flutter动态增删TextFormField异常:删指定项却删最后一项
问题根源与解决方案
核心问题
- 自定义
GTextField未绑定控制器:你的TextFormField没有接收传入的controller参数,导致输入框和控制器完全脱节,这是基础错误。 - Widget复用导致删除错位:使用
for (var ctrl in controllers)遍历生成输入框,且未给每个GTextField设置唯一key,Flutter会复用现有Widget,删除时出现错位。 - 删除逻辑顺序错误:先调用
setState再修改控制器列表,此时UI重建时列表还没更新,导致删除逻辑失效。
修复步骤
1. 修复自定义GTextField
给TextFormField添加controller参数绑定:
import 'package:flutter/material.dart'; class GTextField extends StatelessWidget { final Widget? prefixIcon; final Widget? suffixIcon; final String hintText; final TextEditingController controller; const GTextField({ super.key, this.prefixIcon, this.suffixIcon, required this.hintText, required this.controller, }); @override Widget build(BuildContext context) { return TextFormField( controller: controller, // 新增:绑定控制器 decoration: InputDecoration( hintText: hintText, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15), prefixIcon: prefixIcon, prefixIconConstraints: const BoxConstraints(), suffixIcon: suffixIcon, filled: true, fillColor: Colors.white10.withOpacity(0.05), border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(30), ), errorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red), borderRadius: BorderRadius.circular(30), ), ), ); } }
2. 修改页面代码的遍历与删除逻辑
- 改用索引遍历生成输入框,避免
indexOf的潜在问题; - 给每个
GTextField设置唯一key,防止Widget复用错位; - 调整删除逻辑顺序,先修改列表再调用
setState:
import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gdsc_session2/constants/image_strings.dart'; import '../widgets/text_field.dart'; class NewTask extends StatefulWidget { const NewTask({super.key}); @override State<NewTask> createState() => _NewTaskState(); } class _NewTaskState extends State<NewTask> { final GlobalKey<FormState> formKey = GlobalKey(); final List<TextEditingController> controllers = []; @override void dispose() { // 新增:销毁所有控制器,避免内存泄漏 for (var ctrl in controllers) { ctrl.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Form( key: formKey, child: ListView( children: [ // 改用索引遍历 for (int i = 0; i < controllers.length; i++) ...[ GTextField( key: ValueKey(controllers[i]), // 设置唯一key hintText: 'Todo ${i + 1}', prefixIcon: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: SvgPicture.asset( GImageStrings.clipboard, width: 25, ), ), suffixIcon: IconButton( onPressed: () { setState(() { removeTextField(i); // 先修改列表,再触发重建 }); }, icon: const Icon(Icons.clear)), controller: controllers[i], ), const SizedBox( height: 15, ), ], // Add Button Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 200, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.withOpacity(0.2), ), onPressed: () { setState(() { addTextField(); }); }, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add_circle), SizedBox( width: 10, ), Text( 'Add', style: TextStyle(color: Colors.white), ) ], )), ), ], ) ], ), ), ) ], ), ); } void addTextField() { controllers.add(TextEditingController()); } void removeTextField(int index) { controllers[index].dispose(); // 销毁要删除的控制器 controllers.removeAt(index); } }
额外优化
- 新增了
dispose方法,遍历销毁所有控制器,避免内存泄漏; - 删除单个控制器时先调用
dispose,再从列表移除,进一步避免内存问题。
内容的提问来源于stack exchange,提问作者Ahmed Gamil
相关产品推荐
相关产品推荐

