Flutter/Dart可复用表格组件:如何无类容器传递命名属性列表
几种Flutter中实现表格Widget与数据类解耦的方案
针对你想让表格Widget脱离特定Item类绑定、灵活接收不同数据结构的需求,这里有几个实用的实现思路:
1. 用Map传递数据 + 指定列Key(灵活无绑定)
这种方式直接用List<Map<String, dynamic>>承载数据,让表格通过Key取值渲染,完全不依赖任何自定义数据类。
表格Widget实现:
class GenericTable extends StatelessWidget { final List<Map<String, dynamic>> data; final List<String> columnKeys; // 对应要展示的属性Key final List<String> columnTitles; // 表头文字 const GenericTable({ super.key, required this.data, required this.columnKeys, required this.columnTitles, }) : assert(columnKeys.length == columnTitles.length, "列数需匹配"); @override Widget build(BuildContext context) { return Table( border: TableBorder.all(), children: [ // 表头行 TableRow( children: columnTitles.map((title) => Padding( padding: const EdgeInsets.all(8.0), child: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), )).toList(), ), // 数据行 ...data.map((row) => TableRow( children: columnKeys.map((key) => Padding( padding: const EdgeInsets.all(8.0), child: Text(row[key].toString()), )).toList(), )), ], ); } }
调用示例:
GenericTable( data: [ {"name": "张三", "street": "中山路", "number": 101}, {"name": "李四", "street": "解放路", "number": 202}, ], columnKeys: ["name", "street", "number"], columnTitles: ["姓名", "街道", "门牌号"], )
优点:完全解耦,任何带对应Key的Map都能传;缺点:无类型校验,Key写错会导致空值,适合快速迭代场景。
2. 泛型+回调函数(类型安全,扩展性强)
这种方案用泛型支持任意数据类型,通过回调函数定义每个列如何从数据对象中取值,既保留灵活性又有类型安全保障。
表格Widget实现:
// 定义列配置类,封装表头和取值逻辑 class TableColumn<T> { final String title; final dynamic Function(T item) valueExtractor; const TableColumn({required this.title, required this.valueExtractor}); } class TypedGenericTable<T> extends StatelessWidget { final List<T> items; final List<TableColumn<T>> columns; const TypedGenericTable({ super.key, required this.items, required this.columns, }); @override Widget build(BuildContext context) { return Table( border: TableBorder.all(), children: [ // 表头 TableRow( children: columns.map((col) => Padding( padding: const EdgeInsets.all(8.0), child: Text(col.title, style: const TextStyle(fontWeight: FontWeight.bold)), )).toList(), ), // 数据行 ...items.map((item) => TableRow( children: columns.map((col) => Padding( padding: const EdgeInsets.all(8.0), child: Text(col.valueExtractor(item).toString()), )).toList(), )), ], ); } }
调用示例:
场景1:用Map数据
TypedGenericTable<Map<String, dynamic>>( items: [ {"name": "王五", "street": "建国路", "number": 303}, {"name": "赵六", "street": "复兴路", "number": 404}, ], columns: [ TableColumn(title: "姓名", valueExtractor: (row) => row["name"]), TableColumn(title: "街道", valueExtractor: (row) => row["street"]), TableColumn(title: "门牌号", valueExtractor: (row) => row["number"]), ], )
场景2:用自定义类(比如原有Item类)
// 原有Item类 class Item { final String name; final String street; final int number; Item(this.name, this.street, this.number); } // 调用表格 TypedGenericTable<Item>( items: [ Item("孙七", "光明路", 505), Item("周八", "幸福路", 606), ], columns: [ TableColumn(title: "姓名", valueExtractor: (item) => item.name), TableColumn(title: "街道", valueExtractor: (item) => item.street), TableColumn(title: "门牌号", valueExtractor: (item) => item.number), ], )
优点:类型安全,编译期就能发现取值逻辑错误;支持任意数据类型,扩展性拉满;缺点:需要额外定义列配置类,代码量稍多。
3. 直接传递List(极简但易出错)
如果想贴近你说的List<[name, street, number]>结构,可以用List<List<dynamic>>传递数据,但这种方式没有属性命名,完全依赖顺序匹配,可读性和维护性差,不推荐用于复杂场景:
调用示例(表格实现类似方案1,遍历List而非Map):
GenericListTable( data: [ ["钱九", "朝阳路", 707], ["吴十", "海淀路", 808], ], columnTitles: ["姓名", "街道", "门牌号"], )
优点:代码极简;缺点:顺序错了直接展示混乱,无类型校验,后期维护容易出问题。
综合来看,泛型+回调函数的方案是最优解,既满足解耦需求,又保障类型安全,适合长期维护的项目;如果是快速原型开发,用Map的方案更高效。
内容的提问来源于stack exchange,提问作者harrow
相关产品推荐
相关产品推荐

