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

如何获取ListView.builder循环内TextFormField的输入值

ListView.builder 中使用 TextFormField 绑定列表数据取值方案

核心问题本质

ListView 是懒加载回收机制,列表项滚动出可视区域后会被销毁,进入可视区域时会重新构建,直接在构建方法里临时创建输入框控制器、不做状态和数据源的绑定,必然会出现输入内容错位、滚动丢失、取值错乱的问题。

必守实现规则

  • 每个输入框必须配置基于业务对象唯一ID的ValueKey,禁止直接用列表索引作为Key,避免列表增删、滚动时状态复用错位
  • 禁止在itemBuilder构建方法内临时创建TextEditingController,控制器的生命周期要和页面/业务对象绑定,避免频繁创建销毁导致内容丢失
  • 输入值必须和数据源对象的属性做双向绑定,不要依赖控件临时状态存值

两种常用场景实现

场景1:轻量输入,无需统一表单校验

适合只需要收集输入值、不需要做统一必填/格式校验的场景,直接给每个业务对象绑定专属控制器,实时同步输入值。
首先定义业务数据结构:

class BusinessItem {
  /// 业务对象唯一标识,绝对不能用索引代替
  final String uniqueId;
  /// 初始为空字符串,存储用户输入内容
  String inputValue;
  /// 绑定对应输入框的控制器
  TextEditingController? editingController;

  BusinessItem({
    required this.uniqueId,
    this.inputValue = '',
    this.editingController,
  });
}

页面核心实现代码:

class ListInputDemo extends StatefulWidget {
  const ListInputDemo({super.key});

  @override
  State<ListInputDemo> createState() => _ListInputDemoState();
}

class _ListInputDemoState extends State<ListInputDemo> {
  late final List<BusinessItem> _dataList;

  @override
  void initState() {
    super.initState();
    // 初始化模拟数据,所有inputValue初始为空字符串
    _dataList = List.generate(30, (index) => BusinessItem(uniqueId: 'biz_$index'));
    // 为每个业务对象初始化专属控制器,绑定初始空值
    for (final item in _dataList) {
      item.editingController = TextEditingController(text: item.inputValue);
      // 监听输入,实时同步值到业务对象属性
      item.editingController!.addListener(() {
        item.inputValue = item.editingController!.text;
      });
    }
  }

  @override
  void dispose() {
    // 页面销毁时统一释放所有控制器,避免内存泄漏
    for (final item in _dataList) {
      item.editingController?.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('列表输入 demo')),
      body: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: _dataList.length,
        itemBuilder: (context, index) {
          final item = _dataList[index];
          return Container(
            margin: const EdgeInsets.only(bottom: 12),
            child: TextFormField(
              key: ValueKey(item.uniqueId),
              controller: item.editingController,
              decoration: const InputDecoration(
                hintText: '请输入内容',
                border: OutlineInputBorder(),
              ),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 提交时直接遍历数据源,取每个对象的inputValue即可
          final submitData = _dataList.map((e) => e.inputValue).toList();
          debugPrint('收集到的所有输入值:$submitData');
          // 后续写业务提交逻辑即可
        },
        child: const Icon(Icons.done),
      ),
    );
  }
}

场景2:需要统一表单校验

适合需要做必填校验、输入格式校验,统一提交的场景,用Form组件做全局管理,不需要提前初始化一堆控制器。
核心实现片段:

/// 表单全局key
final _formKey = GlobalKey<FormState>();
late final List<BusinessItem> _dataList = List.generate(30, (index) => BusinessItem(uniqueId: 'biz_$index'));

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Form(
      key: _formKey,
      child: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: _dataList.length,
        itemBuilder: (context, index) {
          final item = _dataList[index];
          return Container(
            margin: const EdgeInsets.only(bottom: 12),
            child: TextFormField(
              key: ValueKey(item.uniqueId),
              initialValue: item.inputValue,
              decoration: const InputDecoration(
                hintText: '请输入内容',
                border: OutlineInputBorder(),
              ),
              // 校验逻辑
              validator: (val) {
                if (val == null || val.trim().isEmpty) {
                  return '输入内容不能为空';
                }
                return null;
              },
              // 实时同步输入值到对象
              onChanged: (val) => item.inputValue = val,
            ),
          );
        },
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        // 统一校验
        if (_formKey.currentState!.validate()) {
          _formKey.currentState!.save();
          // 校验通过后,_dataList里的inputValue已经是最新值,直接拿去提交即可
          final submitData = _dataList.map((e) => e.inputValue).toList();
          debugPrint('校验通过,提交数据:$submitData');
        }
      },
      child: const Icon(Icons.done),
    ),
  );
}

避坑提醒

  • 如果列表支持动态增删项,增删数据时要同步处理对应控制器的创建和释放,保证控制器和业务对象一一对应
  • 不要为了省事儿给所有输入框共用同一个控制器,会导致所有输入框内容同步覆盖
  • 长列表场景优先用第二种方案,减少控制器初始化的内存开销

内容的提问来源于stack exchange,提问作者Houssem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 23:27:11