如何在Flutter中不使用DataTable,仅用Table Widget实现动态表格?
基于Flutter Table Widget实现可编辑(增删、移动)行列的表格方案
核心实现思路
通过StatefulWidget维护表格的二维数据源,结合Table、Row、Column组件动态渲染表格结构,同时为行列添加操作按钮实现增删、移动功能。所有逻辑基于Flutter原生组件,无需第三方库。
完整可运行代码示例
import 'package:flutter/material.dart'; class EditableTable extends StatefulWidget { const EditableTable({super.key}); @override State<EditableTable> createState() => _EditableTableState(); } class _EditableTableState extends State<EditableTable> { // 二维列表维护表格数据,每个元素代表单元格内容 List<List<String>> _tableData = [ ['单元格1-1', '单元格1-2', '单元格1-3'], ['单元格2-1', '单元格2-2', '单元格2-3'], ['单元格3-1', '单元格3-2', '单元格3-3'], ]; // 添加新行 void _addRow() { setState(() { _tableData.add(List.generate(_tableData[0].length, (index) => '')); }); } // 删除指定行 void _deleteRow(int index) { if (_tableData.length > 1) { setState(() { _tableData.removeAt(index); }); } } // 向上移动行 void _moveRowUp(int index) { if (index > 0) { setState(() { final temp = _tableData[index]; _tableData[index] = _tableData[index - 1]; _tableData[index - 1] = temp; }); } } // 向下移动行 void _moveRowDown(int index) { if (index < _tableData.length - 1) { setState(() { final temp = _tableData[index]; _tableData[index] = _tableData[index + 1]; _tableData[index + 1] = temp; }); } } // 添加新列 void _addColumn() { setState(() { for (var row in _tableData) { row.add(''); } }); } // 删除指定列 void _deleteColumn(int index) { if (_tableData[0].length > 1) { setState(() { for (var row in _tableData) { row.removeAt(index); } }); } } // 向左移动列 void _moveColumnLeft(int index) { if (index > 0) { setState(() { for (var row in _tableData) { final temp = row[index]; row[index] = row[index - 1]; row[index - 1] = temp; } }); } } // 向右移动列 void _moveColumnRight(int index) { if (index < _tableData[0].length - 1) { setState(() { for (var row in _tableData) { final temp = row[index]; row[index] = row[index + 1]; row[index + 1] = temp; } }); } } // 更新单元格内容 void _updateCell(int rowIndex, int colIndex, String value) { setState(() { _tableData[rowIndex][colIndex] = value; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('可编辑表格')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ // 全局操作按钮:添加行、列 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton(onPressed: _addRow, child: const Text('添加行')), const SizedBox(width: 16), ElevatedButton(onPressed: _addColumn, child: const Text('添加列')), ], ), const SizedBox(height: 16), // 表格区域,带滚动 Expanded( child: SingleChildScrollView( scrollDirection: Axis.vertical, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Table( border: TableBorder.all(), children: [ // 表头行(带列操作按钮) TableRow( children: [ const TableCell(child: SizedBox(width: 80)), // 占位,对应行操作列 ...List.generate(_tableData[0].length, (colIndex) { return TableCell( child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_left), onPressed: () => _moveColumnLeft(colIndex), ), IconButton( icon: const Icon(Icons.arrow_right), onPressed: () => _moveColumnRight(colIndex), ), IconButton( icon: const Icon(Icons.delete), onPressed: () => _deleteColumn(colIndex), ), Expanded( child: TextField( decoration: const InputDecoration(border: InputBorder.none), onChanged: (value) => _updateCell(0, colIndex, value), controller: TextEditingController(text: _tableData[0][colIndex]), ), ), ], ), ); }), ], ), // 数据行(带行操作按钮) ...List.generate(_tableData.length - 1, (rowIndex) { final actualRowIndex = rowIndex + 1; return TableRow( children: [ // 行操作按钮组 TableCell( child: Column( children: [ IconButton( icon: const Icon(Icons.arrow_upward), onPressed: () => _moveRowUp(actualRowIndex), ), IconButton( icon: const Icon(Icons.arrow_downward), onPressed: () => _moveRowDown(actualRowIndex), ), IconButton( icon: const Icon(Icons.delete), onPressed: () => _deleteRow(actualRowIndex), ), ], ), ), // 单元格内容 ...List.generate(_tableData[actualRowIndex].length, (colIndex) { return TableCell( child: TextField( decoration: const InputDecoration(border: InputBorder.none), onChanged: (value) => _updateCell(actualRowIndex, colIndex, value), controller: TextEditingController(text: _tableData[actualRowIndex][colIndex]), ), ); }), ], ); }), ], ), ), ), ), ], ), ), ); } } // 用于运行的入口 void main() { runApp(const MaterialApp(home: EditableTable())); }
关键功能解析
数据源维护
使用List<List<String>>作为表格的核心数据源,每一层列表对应一行数据,列表中的每个元素对应单元格内容。所有增删、移动操作都是通过修改这个二维列表,再调用setState触发UI重建。行列增删逻辑
- 添加行:向二维列表中添加一个新的子列表,长度与现有列数一致
- 删除行:移除指定索引的子列表,同时限制最少保留1行
- 添加列:遍历每一行,向子列表末尾添加空字符串
- 删除列:遍历每一行,移除指定索引的元素,限制最少保留1列
行列移动逻辑
- 移动行:交换目标行与相邻行的位置(上移交换
index和index-1,下移交换index和index+1) - 移动列:遍历所有行,交换目标列与相邻列的元素位置
- 移动行:交换目标行与相邻行的位置(上移交换
动态表格渲染
通过List.generate动态生成TableRow和TableCell,为表头和每一行添加对应的操作按钮,单元格使用TextField支持内容编辑,确保表格内容可修改。
注意事项
- 代码中加入了边界判断,避免删除最后一行/列或越界移动的操作
- 使用
SingleChildScrollView包裹Table,实现表格在横竖方向的滚动,适配不同屏幕尺寸 - 如果不需要单元格编辑,可将
TextField替换为Text组件
内容的提问来源于stack exchange,提问作者Maria Grigoryan
相关产品推荐
相关产品推荐

