You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 12:35:16