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

Flutter中如何实现Table的创建、行增删及内容编辑功能

Flutter 动态可编辑Table实现方案

不需要依赖第三方包,用Flutter原生组件搭配状态驱动的思路就能完整实现新增、删除、行编辑能力,实现逻辑比套第三方封装的组件更可控。

核心实现逻辑

所有表格渲染完全基于可维护的状态数据列表,所有增删改操作先同步修改数据列表,再触发UI重渲染,从根源避免第三方组件封装过死、自定义能力不足的问题:

  • 每一行数据绑定全局唯一ID,作为渲染key避免增删时的状态错位
  • 单元格默认替换为可输入组件,直接和对应行的数据字段双向绑定
  • 所有操作只需要维护数据列表,不需要操作表格组件本身的实例

可直接复用的代码实现

1. 定义行数据结构

class EditableTableRow {
  final String rowId; // 行唯一标识,禁止用索引代替
  String username;
  int age;
  String remark;

  EditableTableRow({
    required this.rowId,
    required this.username,
    required this.age,
    required this.remark,
  });
}

2. 核心页面组件

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

  @override
  State<DynamicTablePage> createState() => _DynamicTablePageState();
}

class _DynamicTablePageState extends State<DynamicTablePage> {
  // 表格数据源
  final List<EditableTableRow> _tableData = [];

  @override
  void initState() {
    super.initState();
    // 初始化默认1行示例数据
    _tableData.add(EditableTableRow(
      rowId: DateTime.now().millisecondsSinceEpoch.toString(),
      username: '',
      age: 0,
      remark: '',
    ));
  }

  // 新增行
  void _addRow() {
    setState(() {
      _tableData.add(EditableTableRow(
        rowId: DateTime.now().millisecondsSinceEpoch.toString(),
        username: '',
        age: 0,
        remark: '',
      ));
    });
  }

  // 删除指定行
  void _removeRow(String rowId) {
    setState(() {
      _tableData.removeWhere((item) => item.rowId == rowId);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('动态可编辑表格')),
      body: SingleChildScrollView(
        // 宽表格支持横向滚动
        scrollDirection: Axis.horizontal,
        child: SingleChildScrollView(
          child: DataTable(
            columns: const [
              DataColumn(label: Text('用户名')),
              DataColumn(label: Text('年龄')),
              DataColumn(label: Text('备注')),
              DataColumn(label: Text('操作')),
            ],
            rows: _tableData.map<DataRow>((rowItem) {
              return DataRow(
                // 必须用唯一ID当key,防止输入焦点错位
                key: ValueKey(rowItem.rowId),
                cells: [
                  DataCell(
                    TextField(
                      controller: TextEditingController(text: rowItem.username)
                        ..selection = TextSelection.collapsed(offset: rowItem.username.length),
                      onChanged: (val) => rowItem.username = val,
                      decoration: const InputDecoration(border: InputBorder.none),
                    ),
                  ),
                  DataCell(
                    TextField(
                      controller: TextEditingController(text: rowItem.age == 0 ? '' : rowItem.age.toString())
                        ..selection = TextSelection.collapsed(offset: rowItem.age.toString().length),
                      keyboardType: TextInputType.number,
                      onChanged: (val) => rowItem.age = int.tryParse(val) ?? 0,
                      decoration: const InputDecoration(border: InputBorder.none),
                    ),
                  ),
                  DataCell(
                    TextField(
                      controller: TextEditingController(text: rowItem.remark)
                        ..selection = TextSelection.collapsed(offset: rowItem.remark.length),
                      onChanged: (val) => rowItem.remark = val,
                      decoration: const InputDecoration(border: InputBorder.none),
                    ),
                  ),
                  DataCell(
                    IconButton(
                      icon: const Icon(Icons.delete_outline, color: Colors.red),
                      onPressed: () => _removeRow(rowItem.rowId),
                    ),
                  ),
                ],
              );
            }).toList(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addRow,
        child: const Icon(Icons.add),
      ),
    );
  }
}

优化提示

  • 数据量超过20行时,不要全量用setState刷新,可以把每一行抽成独立组件,用ValueNotifier做单元格级别的局部刷新,减少性能损耗
  • 输入框的TextEditingController可以抽成和行绑定的实例,在组件dispose时统一释放,避免长时间使用后的内存泄漏
  • 需要固定列、排序、选中能力时,直接在现有逻辑基础上扩展即可,不需要引入额外依赖

不要用行索引作为DataRow的key,增删行时索引会发生变化,直接导致输入框焦点错位、内容串位的问题,这是绝大多数人写动态可编辑表格最容易踩的坑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 07:15:33