如何获取Flutter ListView中各TextFormField的输入数据?
解决方案
1. 创建数据模型类
先定义一个PackageItem类,用来存储每个包裹表单的输入数据和对应的控制器,确保每个表单的数据相互独立:
class PackageItem { final TextEditingController quantityController; final TextEditingController weightController; final TextEditingController heightController; PackageItem() : quantityController = TextEditingController(), weightController = TextEditingController(), heightController = TextEditingController(); // 销毁控制器,避免内存泄漏 void dispose() { quantityController.dispose(); weightController.dispose(); heightController.dispose(); } }
2. 修改状态类变量
替换原来的全局变量和零散列表,改用包裹实例列表管理所有表单:
class _AddItemsToRequestState extends State<AddItemsToRequest> { List<PackageItem> packageItems = []; RegExp regExp = RegExp('[a-zA-Z]'); // 修正正则表达式,避免匹配非字母字符 // ... 其他代码 }
3. 重构表单组件
修改itemForm方法,接收PackageItem参数,将输入框与对应实例的控制器绑定:
Widget itemForm(PackageItem item) { return Column(children: [ Container( decoration: BoxDecoration(color: grey, border: Border.all(color: black)), width: double.infinity, child: const Center( child: Text('Package details', style: TextStyle( fontWeight: FontWeight.bold, backgroundColor: grey, fontSize: 24)), )), Row( children: [ Flexible( child: TextFormField( controller: item.quantityController, keyboardType: TextInputType.number, validator: (value) { if (value == null || value.isEmpty) { return 'Quantity cannot be empty'; } if (int.tryParse(value) == null) { return 'Please enter a valid number'; } return null; }, autovalidateMode: AutovalidateMode.onUserInteraction, decoration: const InputDecoration( errorStyle: TextStyle(color: Colors.redAccent), border: OutlineInputBorder( borderSide: BorderSide(), borderRadius: BorderRadius.all( Radius.circular(0.0), ), ), fillColor: Color.fromARGB(255, 238, 238, 238), filled: true, focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.blueAccent, width: 2.5), ), hintText: "Quantity : "), ), ), Flexible( child: TextFormField( controller: item.weightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: (value) { if (value == null || value.isEmpty) { return 'Weight cannot be empty'; } if (regExp.hasMatch(value) || double.tryParse(value) == null) { return 'Please enter a valid number'; } return null; }, autovalidateMode: AutovalidateMode.onUserInteraction, decoration: const InputDecoration( errorStyle: TextStyle(color: Colors.redAccent), border: OutlineInputBorder( borderSide: BorderSide(), borderRadius: BorderRadius.all( Radius.circular(0.0), ), ), fillColor: Color.fromARGB(255, 238, 238, 238), filled: true, focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.blueAccent, width: 2.5), ), hintText: "Weight : "), ), ), Flexible( child: TextFormField( controller: item.heightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: (value) => value!.isEmpty ? 'Height cannot be empty' : null, autovalidateMode: AutovalidateMode.onUserInteraction, decoration: const InputDecoration( errorStyle: TextStyle(color: Colors.redAccent), border: OutlineInputBorder( borderSide: BorderSide(), borderRadius: BorderRadius.all( Radius.circular(0.0), ), ), fillColor: Color.fromARGB(255, 238, 238, 238), filled: true, focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.blueAccent, width: 2.5), ), hintText: "Height : "), ), ), ], ), ]); }
4. 修改添加按钮逻辑
点击添加按钮时,创建新的PackageItem实例并加入列表:
Widget _addTile() { return ElevatedButton( child: const Icon(Icons.add), onPressed: () { setState(() { packageItems.add(PackageItem()); }); }); }
5. 更新ListView构建
基于packageItems列表渲染表单,删除时同步销毁对应控制器:
Widget _listView() { return ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, itemCount: packageItems.length, itemBuilder: (context, index) { final item = packageItems[index]; return Dismissible( key: Key('package_$index'), // 使用索引生成稳定的key onDismissed: (direction) { setState(() { packageItems[index].dispose(); // 销毁控制器释放资源 packageItems.removeAt(index); }); ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Package removed'))); }, background: Container( color: const Color.fromARGB(255, 210, 31, 19), child: const Center( child: Text( 'Remove ', style: TextStyle( color: white, fontWeight: FontWeight.bold, fontSize: 32), ), )), child: Container( width: double.infinity, margin: const EdgeInsets.all(5), child: itemForm(item), ), ); }, ); }
6. 获取所有表单数据并上传Firebase
在OK按钮点击事件中,遍历所有包裹实例,提取输入值后上传:
Widget _okButton() { return ElevatedButton( onPressed: () { // 收集所有有效包裹数据 List<Map<String, dynamic>> allPackages = []; for (var item in packageItems) { int? quantity = int.tryParse(item.quantityController.text); double? weight = double.tryParse(item.weightController.text); double? height = double.tryParse(item.heightController.text); if (quantity != null && weight != null && height != null) { allPackages.add({ 'quantity': quantity, 'weight': weight, 'height': height, }); } } // 此处添加Firebase上传逻辑,示例: // FirebaseFirestore.instance.collection('shipping_requests').add({'packages': allPackages}); print(allPackages); // 测试输出 Navigator.of(context).pop(); }, child: const Text("OK"), ); }
7. 页面销毁时清理资源
重写dispose方法,销毁所有控制器避免内存泄漏:
@override void dispose() { for (var item in packageItems) { item.dispose(); } super.dispose(); }
核心改进说明
- 用独立的
PackageItem实例管理每个表单的控制器和数据,避免全局变量覆盖问题 - 通过
TextEditingController直接获取输入值,无需依赖onChanged回调 - 增加控制器销毁逻辑,避免内存泄漏
- 优化输入验证,确保输入内容为有效数字
内容的提问来源于stack exchange,提问作者louks
相关产品推荐
相关产品推荐

