Flutter动态列表增删时TextField/DropdownMenu数据丢失及序号更新问题
解决Flutter动态列表增删时输入内容丢失与序号更新问题
问题说明
实现了支持动态增删的Flutter列表,初始含一个列表项,用户可点击按钮添加新项或滑动删除现有项,但存在两个问题:
- 添加/删除列表项时,DropdownMenu中的用户输入内容会丢失
- 删除项后,剩余列表项的序号无法自动更新为正确值
核心修复思路
- 数据与UI解耦:将每个列表项的输入数据封装为独立Model类,确保输入状态跟随数据而非Widget实例
- 稳定的Widget Key:使用Model的唯一标识作为列表项和Dismissible的Key,避免Flutter因Key变化重建Widget导致状态丢失
- 动态计算序号:不在Model中存储序号,而是在构建列表时通过当前索引动态计算,确保增删后序号自动更新
修改后的完整代码
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } // 列表项数据模型,存储输入状态 class InvoiceItemModel { final String id; TextEditingController itemController; InvoiceItemModel({required this.id}) : itemController = TextEditingController(); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late List<InvoiceItemModel> itemsList; @override void initState() { itemsList = [InvoiceItemModel(id: 'item_1')]; super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: SingleChildScrollView( child: Column( children: [ ListView.builder( shrinkWrap: true, itemBuilder: (BuildContext context, int index) { final item = itemsList[index]; return Dismissible( key: ValueKey(item.id), child: InvoiceItemInput( parentWidth: 400.0, index: index + 1, // 动态计算当前序号 controller: item.itemController, ), background: Container(color: Colors.red), direction: DismissDirection.endToStart, onDismissed: (direction) { if (direction == DismissDirection.endToStart) { setState(() { itemsList.removeAt(index); }); } }, ); }, itemCount: itemsList.length, ), Padding( padding: const EdgeInsets.symmetric(vertical: 20.0), child: Align( alignment: Alignment.centerRight, child: OutlinedButton( child: const Text('ADD'), onPressed: () { setState(() { itemsList.add(InvoiceItemModel(id: 'item_${itemsList.length + 1}')); }); }, ), ), ), ], ), ), ), ); } } // 改为无状态Widget,接收外部传入的控制器和序号 class InvoiceItemInput extends StatelessWidget { const InvoiceItemInput({ super.key, required this.parentWidth, required this.index, required this.controller, }); final double parentWidth; final int index; final TextEditingController controller; final double horizontalSpacing = 15.0; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 12.0), Container( padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 7.0), child: Text( 'Item $index', style: Theme.of(context) .textTheme .labelLarge ?.copyWith(color: Colors.white), ), decoration: BoxDecoration( color: Colors.lightBlue, borderRadius: BorderRadius.circular(7.0), ), ), const SizedBox(height: 7.0), Wrap( spacing: horizontalSpacing, runSpacing: 25.0, children: [ SizedBox( width: (parentWidth - horizontalSpacing) / 2 < 200.0 ? (parentWidth - horizontalSpacing) / 2 : 200.0, child: DropdownMenu( controller: controller, label: const Text( 'Item Name *', style: TextStyle( fontFamily: 'Raleway', fontSize: 14.0, color: Colors.black87, fontWeight: FontWeight.w500, ), ), hintText: 'Enter Item Name', requestFocusOnTap: true, enableFilter: true, expandedInsets: EdgeInsets.zero, textStyle: Theme.of(context).textTheme.bodySmall, menuStyle: MenuStyle( backgroundColor: WidgetStateProperty.all(Colors.lightBlue), ), dropdownMenuEntries: [ DropdownMenuEntry( value: 'Pen', label: 'Pen', style: MenuItemButton.styleFrom( foregroundColor: Colors.white, textStyle: Theme.of(context).textTheme.bodySmall, ), ), DropdownMenuEntry( value: 'Pencil', label: 'Pencil', style: MenuItemButton.styleFrom( foregroundColor: Colors.white, textStyle: Theme.of(context).textTheme.bodySmall, ), ) ], ), ), ], ), const SizedBox(height: 15.0), ], ); } }
关键修改点
- 新增
InvoiceItemModel类:存储每个列表项的唯一ID和输入控制器,让输入状态与数据绑定,避免Widget重建丢失内容 - 替换Widget Key:使用Model的固定ID作为Key,替代原代码中动态生成的
DateTime.now(),确保Flutter能正确复用列表项Widget - 动态计算序号:在构建
InvoiceItemInput时直接传入index+1,删除或添加项后序号自动同步更新 - 简化列表项Widget:将
InvoiceItemInput改为无状态组件,输入控制器由父组件传入,状态统一由Model管理,避免StatefulWidget的状态被意外重置
内容的提问来源于stack exchange,提问作者pbs
相关产品推荐
相关产品推荐

