Flutter Provider复用组件更新异常:单组件及ListView场景解决方案问询
解决Flutter复用组件TextField输入同步更新所有显示的问题
当前所有ReusableTile的显示都绑定到Provider里的同一个_number变量,无论哪个tile触发更新,都会覆盖这个变量并通知所有监听者,导致所有显示同步变化。要解决这个问题,需要让每个tile拥有独立的状态存储,通过唯一标识区分不同tile的数据。
一、Column中使用复用组件的解决方案
1. 修改Provider状态管理
将单个数值变量改为Map结构,存储每个tile对应的数值,同时更新方法接收tile的唯一标识和输入值:
class AppState extends ChangeNotifier { // 用Map存储每个tile的数值,key为tile的唯一ID Map<String, double> _tileNumbers = {}; // 根据tileID获取对应数值,默认返回0.0 double getNumber(String tileId) { return _tileNumbers[tileId] ?? 0.0; } // 更新指定tile的数值 void updateTileNumber(String tileId, String inputText) { _tileNumbers[tileId] = double.tryParse(inputText) ?? 0.0; notifyListeners(); } }
2. 主页面修改
给每个ReusableTile传入唯一ID,同时保留各自的TextEditingController:
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State<MyHomePage> createState() => _MyHomePageState(); } // 保持每个tile独立的输入控制器 final tile1Controller = TextEditingController(); final tile2Controller = TextEditingController(); final tile3Controller = TextEditingController(); class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ ReusableTile(tileId: 'tile_1', title: 'Tile 1', controller: tile1Controller), ReusableTile(tileId: 'tile_2', title: 'Tile 2', controller: tile2Controller), ReusableTile(tileId: 'tile_3', title: 'Tile 3', controller: tile3Controller), ], ), ); } }
3. 修改ReusableTile组件
新增tileId参数,更新时传入该标识,Consumer根据tileId获取对应数值:
class ReusableTile extends StatelessWidget { final String tileId; final String title; final TextEditingController controller; const ReusableTile({ super.key, required this.tileId, required this.title, required this.controller, }); @override Widget build(BuildContext context) { final provider = Provider.of<AppState>(context, listen: false); return Container( padding: const EdgeInsets.all(5), child: ListTile( tileColor: Colors.blueAccent, title: Text( title, style: const TextStyle(color: Colors.white), ), subtitle: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: TextField( controller: controller, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( labelText: 'Number', hintText: 'Enter number', labelStyle: TextStyle(color: Colors.white), hintStyle: TextStyle(color: Colors.white70), ), style: const TextStyle(color: Colors.white), ), ), IconButton( onPressed: () { provider.updateTileNumber(tileId, controller.text); }, icon: const Icon(Icons.done, color: Colors.white), ), ], ), trailing: Consumer<AppState>( builder: (context, state, child) { return Text( '${state.getNumber(tileId)}', style: const TextStyle(color: Colors.white), ); }, ), ), ); } }
二、ListView.builder中使用复用组件的解决方案
核心是给每个列表项分配唯一标识(比如索引index或数据模型的id),复用上述修改后的Provider和ReusableTile即可:
主页面代码示例
class MyListViewPage extends StatefulWidget { const MyListViewPage({Key? key}) : super(key: key); @override State<MyListViewPage> createState() => _MyListViewPageState(); } class _MyListViewPageState extends State<MyListViewPage> { // 生成对应数量的输入控制器,与列表项一一对应 final List<TextEditingController> _controllers = List.generate(5, (index) => TextEditingController()); @override void dispose() { // 销毁所有控制器避免内存泄漏 for (var controller in _controllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: 5, itemBuilder: (context, index) { return ReusableTile( tileId: 'tile_$index', title: 'Tile ${index + 1}', controller: _controllers[index], ); }, ), ); } }
说明
- 用
List.generate创建与列表项数量匹配的TextEditingController,确保每个输入框独立 - 以
'tile_$index'作为唯一标识,也可以用业务数据中的唯一ID(如接口返回的id)替代索引 - Provider和ReusableTile的代码与Column场景完全一致,无需额外修改
内容的提问来源于stack exchange,提问作者Ebitu Bonadventure
相关产品推荐
相关产品推荐

