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

Flutter 动态Widget列表实现添加与删除表单行的最佳方案是什么

最优实现方案

原有代码存在3个核心问题:

  • 直接在State中缓存Row Widget列表,且在build方法中反复追加元素,会导致重复渲染的bug
  • 未维护表单数据源与TextField输入状态,删除/新增行时用户已输入的内容会丢失
  • 动态生成的行未指定唯一Key,列表刷新时会出现Widget复用混乱
核心改造思路
  • 数据源与视图分离:在State中维护表单数据源,每个条目绑定对应的TextEditingController来持有TextField的输入值
  • 动态生成视图:每次build时直接根据当前数据源生成行Widget,不做Widget缓存
  • 操作直接作用于数据源:新增/删除操作仅修改数据源,调用setState触发视图自动刷新
  • 资源释放:在dispose生命周期释放所有TextEditingController避免内存泄漏
完整改造代码
import 'package:flutter/material.dart';

class EditJdataForm2 extends StatefulWidget {
  final tags = <MapEntry<String, String>>{
    const MapEntry('city', 'Chicago'),
    const MapEntry('name', 'John'),
    const MapEntry('fullname', 'Doe'),
    const MapEntry('age', '30')
  };

  @override
  _EditJdataForm2State createState() => _EditJdataForm2State();
}

class _EditJdataForm2State extends State<EditJdataForm2> {
  // 维护表单数据源,每个条目绑定文本控制器持有输入值
  final List<MapEntry<String, TextEditingController>> _formDataSource = [];

  @override
  void initState() {
    super.initState();
    // 初始化数据源,绑定控制器
    for (var tag in widget.tags) {
      _formDataSource.add(MapEntry(
        tag.key,
        TextEditingController(text: tag.value),
      ));
    }
  }

  @override
  void dispose() {
    // 释放所有控制器,避免内存泄漏
    for (var entry in _formDataSource) {
      entry.value.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // 每次build动态生成行列表,不做缓存
    List<Widget> formRows = _formDataSource.asMap().entries.map((item) {
      int index = item.key;
      var entry = item.value;
      return Row(
        // 给每一行加唯一Key,避免Widget复用混乱
        key: ValueKey('${entry.key}_$index'),
        children: [
          Expanded(
            flex: 3,
            child: Container(
              margin: const EdgeInsets.only(left: 30.0, right: 30.0),
              child: Text(entry.key,
                  style: const TextStyle(
                    color: Colors.black54,
                    fontSize: 20.0,
                    letterSpacing: 0,
                    fontWeight: FontWeight.normal,
                  )),
            ),
          ),
          Expanded(
              flex: 6,
              child: Container(
                  margin: const EdgeInsets.only(right: 30.0),
                  child: TextFormField(
                    // 绑定控制器持有输入值
                    controller: entry.value,
                  ))),
          Container(
            margin: const EdgeInsets.only(right: 30.0),
            child: Ink(
              decoration: const ShapeDecoration(
                color: Colors.red,
                shape: CircleBorder(),
              ),
              child: IconButton(
                icon: const Icon(
                  Icons.remove,
                  color: Colors.white,
                ),
                onPressed: () {
                  // 删除指定行:释放控制器+移除数据源+刷新视图
                  setState(() {
                    _formDataSource[index].value.dispose();
                    _formDataSource.removeAt(index);
                  });
                },
              ),
            ),
          ),
        ],
      );
    }).toList();

    // 加入新增按钮
    formRows.add(
      Container(
        margin: const EdgeInsets.only(top: 20, left: 30, right: 30),
        width: double.infinity,
        child: ElevatedButton.icon(
          icon: const Icon(Icons.add),
          label: const Text('新增一行'),
          onPressed: () {
            // 新增行:加入新的数据源条目+刷新视图
            setState(() {
              int newIndex = _formDataSource.length;
              _formDataSource.add(MapEntry(
                'newTag$newIndex',
                TextEditingController(text: ''),
              ));
            });
          },
        ),
      ),
    );

    return SingleChildScrollView(
      child: Column(children: formRows),
    );
  }

  // 可调用该方法获取最终表单提交数据
  Map<String, String> getFormData() {
    Map<String, String> result = {};
    for (var entry in _formDataSource) {
      result[entry.key] = entry.value.text;
    }
    return result;
  }
}
补充说明
  • 如果需要自定义新增行的Label,可在新增按钮点击时弹出输入框让用户输入自定义Key,再加入数据源即可
  • 如果需要支持编辑Label,可把展示Key的Text组件换成TextField,同样绑定对应的控制器即可
  • 提交表单时调用getFormData()方法即可直接获取所有字段的键值对

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 21:06:03