Flutter的DataTable小组件。显示数据的指南

2,401 阅读10分钟

Flutter是一个流行的、灵活的、功能齐全的框架,用于构建跨平台应用程序。它最初是作为一个跨平台的移动应用开发框架,特别是用于构建Android和iOS应用,但现在我们也可以用Flutter来构建Linux、macOS和Windows的本地桌面应用。

在大多数应用程序中,程序员必须以表格结构显示一些数据--他们可能需要显示简单的列表、带有一些动作的列表或可编辑的列表。

Flutter带有自己的UI工具包,里面有很多小部件,可以做各种事情。Flutter提供的这样一个小组件是DataTable小组件,用于显示表格式数据结构。与原生平台特定的列表视图相比,DataTable小组件非常灵活。

在本教程中,我将解释DataTable小组件的原理,并通过向你展示几个实际例子来讨论其所有功能。

DataTable的原理和语法

您可以创建一个新的Flutter应用程序或打开一个现有的应用程序来开始学习本教程。如果您想创建一个新的应用程序,请像往常一样使用以下命令。

flutter create datatable-demo 

您也可以使用FlutLab来更快地尝试即将到来的代码片段,甚至不需要安装Flutter。

DataTable小组件有三个关键的子小组件。DataColumn,DataRow, 和DataCell。DataColumn定义了列,DataRow定义了行,而DataCell定义了行内的单元。

DataTable小组件有以下语法。

DataTable(
  columns: [...] // A list of DataColumns
  rows: [...] // A list of DataRows
  ...
  ...
  // other parameters
  ...
) 

Flutter DataTable 教程

让我们用DataTable建立一个简单的书单。通过替换现有代码,将以下代码添加到您的main.dart 文件中。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable()
          ],
        ),
      ),
    );
  }
DataTable _createDataTable() {
    return DataTable(columns: _createColumns(), rows: _createRows());
  }
List<DataColumn> _createColumns() {
    return [      DataColumn(label: Text('ID')),      DataColumn(label: Text('Book')),      DataColumn(label: Text('Author'))    ];
  }
List<DataRow> _createRows() {
    return [      DataRow(cells: [        DataCell(Text('#100')),        DataCell(Text('Flutter Basics')),        DataCell(Text('David John'))      ]),
      DataRow(cells: [
        DataCell(Text('#101')),
        DataCell(Text('Dart Internals')),
        DataCell(Text('Alex Wick'))
      ])
    ];
  }
}

一旦你保存该文件,你将看到一个图书列表,如下图所示。

The book list in our DataTable demo
这里我们创建了一个有三列两行的简单书单。DataTable创建者的代码被分解成两个函数:_createColumns ,用于生成列,_createRows ,用于生成带有单元格数据的行。

这个表有硬编码的模拟数据用于演示,但你可以根据RESTful API请求、设备文件和动态生成的数据来填充行-列数据。

在这些情况下,你可能需要根据Dart列表和地图动态生成行-列数据。下面的代码从列表和地图中渲染了同一个图书列表。我们将尝试在列表中添加新书。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  List<Map> _books = [    {      'id': 100,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 102,      'title': 'Git and GitHub',      'author': 'Merlin Nick'    },    {      'id': 101,      'title': 'Flutter Basics',      'author': 'David John'    },  ];
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable()
          ],
        ),
      ),
    );
  }
DataTable _createDataTable() {
    return DataTable(columns: _createColumns(), rows: _createRows());
  }
List<DataColumn> _createColumns() {
    return [      DataColumn(label: Text('ID')),      DataColumn(label: Text('Book')),      DataColumn(label: Text('Author'))    ];
  }
List<DataRow> _createRows() {
    return _books
        .map((book) => DataRow(cells: [
              DataCell(Text('#' + book['id'].toString())),
              DataCell(Text(book['title'])),
              DataCell(Text(book['author']))
            ]))
        .toList();
  }
}

基本样式和配置

我们只提供了行和列数据来创建上述数据表。因此,Flutter框架通过应用默认的样式和配置渲染了该表。

不过,DataTable小组件非常灵活,所以我们可以根据自己的需要来定制它--通过提供各种参数。例如,我们可以用以下代码配置DataTable小组件的几种样式和配置。用下面的内容更新你的main.dart 文件。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable()
          ],
        ),
      ),
    );
  }
}
DataTable _createDataTable() {
  return DataTable(columns: _createColumns(), 
      rows: _createRows(), 
      dividerThickness: 5, 
      dataRowHeight: 80,
      showBottomBorder: true,
      headingTextStyle: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white
                        ),
      headingRowColor: MaterialStateProperty.resolveWith(
                        (states) => Colors.black
                      ),
  );
}
List<DataColumn> _createColumns() {
  return [    DataColumn(label: Text('ID'), tooltip: 'Book identifier'),    DataColumn(label: Text('Book')),    DataColumn(label: Text('Author'))  ];
}
List<DataRow> _createRows() {
  return [    DataRow(cells: [      DataCell(Text('#100')),      DataCell(Text('Flutter Basics', style: TextStyle(fontWeight: FontWeight.bold))),      DataCell(Text('David John'))    ]),
    DataRow(cells: [
      DataCell(Text('#101')),
      DataCell(Text('Dart Internals')),
      DataCell(Text('Alex Wick'))
    ])
  ];
}

现在,你已经定制了你的DataTable,如下图所示。

Customized DataTable demo

定制的细节。

  • 行的分隔符的厚度由DataTable的dividerThickness 参数增加。
  • 通过DataTable的headingRowColorheadingTextStyle 参数改变了标题行的背景颜色、文本颜色和文本重量。
  • 通过将DataTable的showBottomBorder 参数设置为启用页脚行。true
  • 由于DataColumn的tooltip 参数,第一列得到了一个漂亮的工具提示。

如上所示,您可以按照您的意愿定制数据表。如果您维护一个自定义的Flutter主题,您可以在您的主题数据对象中用DataTableThemeData类定义这些调整。

添加排序和全选功能

当您的数据表包括数值时,排序是一个必须具备的功能,以增加可用性。有时,程序员也会在表的行中添加复选框以实现选择。我们可以通过添加一次选择/取消选择所有项目的功能来提高应用程序的可用性和生产力。

让我们把这些功能添加到我们的书单中吧!

启用排序功能

排序功能是DataTable小组件中的一个内置功能。你可以通过定义主排序列索引和可排序列的onSort 回调函数来启用排序功能。

在你的main.dart 文件中添加以下代码。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  List<Map> _books = [    {      'id': 100,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 101,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 102,      'title': 'Git and GitHub',      'author': 'Merlin Nick'    }  ];
  int _currentSortColumn = 0;
  bool _isSortAsc = true;
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable()
          ],
        ),
      ),
    );
  }
DataTable _createDataTable() {
    return DataTable(
      columns: _createColumns(),
      rows: _createRows(),
      sortColumnIndex: _currentSortColumn,
      sortAscending: _isSortAsc,
    );
  }
List<DataColumn> _createColumns() {
    return [      DataColumn(        label: Text('ID'),        onSort: (columnIndex, _) {          setState(() {            _currentSortColumn = columnIndex;            if (_isSortAsc) {              _books.sort((a, b) => b['id'].compareTo(a['id']));
            } else {
              _books.sort((a, b) => a['id'].compareTo(b['id']));
            }
            _isSortAsc = !_isSortAsc;
          });
        },
      ),
      DataColumn(label: Text('Book')),
      DataColumn(label: Text('Author'))
    ];
  }
List<DataRow> _createRows() {
    return _books
        .map((book) => DataRow(cells: [
              DataCell(Text('#' + book['id'].toString())),
              DataCell(Text(book['title'])),
              DataCell(Text(book['author']))
            ]))
        .toList();
  }
}

正如你所看到的,上面的代码定义了一个排序函数,根据排序方向对书籍列表进行排序。当用户点击ID列头时,该排序函数通过setState 方法改变排序方向。

如果你运行上述代码,你会看到以下结果。你可以点击ID列来对数据表的行进行排序。

Demo of the ID column sort function

启用选择功能

您不需要手动向您的数据表添加复选框部件来启用基于复选框的选择--Flutter DataTable提供了基于复选框的选择这一功能您只需要为DataRow的onSelectChanged 参数添加一个回调,并通过DataRow的selected 参数设置选择状态,以启用基于复选框的选择功能。

添加下面的代码到你的main.dart ,看看这个功能的作用。

import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  // The following list is already sorted by id
  List<Map> _books = [    {      'id': 100,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 101,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 102,      'title': 'Git and GitHub',      'author': 'Merlin Nick'    }  ];
  List<bool> _selected = [];
@override
  void initState() {
    super.initState();
    _selected = List<bool>.generate(_books.length, (int index) => false);
  }
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable()
          ],
        ),
      ),
    );
  }
DataTable _createDataTable() {
    return DataTable(columns: _createColumns(), rows: _createRows());
  }
List<DataColumn> _createColumns() {
    return [      DataColumn(label: Text('ID')),      DataColumn(label: Text('Book')),      DataColumn(label: Text('Author'))    ];
  }
List<DataRow> _createRows() {
    return _books
        .mapIndexed((index, book) => DataRow(
                cells: [
                  DataCell(Text('#' + book['id'].toString())),
                  DataCell(Text(book['title'])),
                  DataCell(Text(book['author']))
                ],
                selected: _selected[index],
                onSelectChanged: (bool? selected) {
                  setState(() {
                    _selected[index] = selected!;
                  });
                }))
        .toList();
  }
}

上面的代码在selected 列表里面存储了当前所选行的索引细节。同时,它通过DataRow的selected 参数设置当前行是否被选中。onSelectChanged 回调函数根据用户的操作更新选择指数列表。Flutter框架自动处理选择所有复选框的动作。

运行上述代码。你会看到如下的结果。

Demo of the selected row in the DataTable

你可以从selected 列表中找到所有的选择指数。

将图片和其他部件添加到数据表中

在前面的例子中,我们用Text widget来显示单元格的内容。有时,程序员必须用数据表显示一些图标、按钮、链接等。像其他复杂的Flutter部件一样,也可以在数据表内显示部件。

让我们通过创建一个名为Category 的新列,在数据单元格内添加一个图像。为了演示,下面的代码将把Flutter标志添加到类别列中。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  List<Map> _books = [    {      'id': 100,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 102,      'title': 'Git and GitHub',      'author': 'Merlin Nick'    },    {      'id': 101,      'title': 'Flutter Basics',      'author': 'David John'    },  ];
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable()
          ],
        ),
      ),
    );
  }
DataTable _createDataTable() {
    return DataTable(columns: _createColumns(), rows: _createRows());
  }
List<DataColumn> _createColumns() {
    return [      DataColumn(label: Text('ID')),      DataColumn(label: Text('Book')),      DataColumn(label: Text('Author')),      DataColumn(label: Text('Category'))    ];
  }
List<DataRow> _createRows() {
    return _books
        .map((book) => DataRow(cells: [
              DataCell(Text('#' + book['id'].toString())),
              DataCell(Text(book['title'])),
              DataCell(Text(book['author'])),
              DataCell(FlutterLogo())
            ]))
        .toList();
  }
}

一旦你执行上述代码,你会看到以下输出。

Add more widgets to the DataTable

同样地,您可以通过简单地将所需的部件传递到DataCell的构造函数中,将任何部件添加到数据单元中。

用数据单元格显示动态内容

有时,我们必须根据用户的操作来动态地改变单元格的数据。例如,当编辑模式被激活时,我们可以让用户编辑一些单元格值。

我们可以通过添加一个复选框来启用/禁用编辑模式,将这种编辑模式功能添加到我们的图书列表中。一旦编辑模式被启用,书名将变成可编辑的文本框。

将以下代码添加到main.dart 文件中,使该例子运行。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  List<Map> _books = [    {      'id': 100,      'title': 'Flutter Basics',      'author': 'David John'    },    {      'id': 102,      'title': 'Git and GitHub',      'author': 'Merlin Nick'    },    {      'id': 101,      'title': 'Flutter Basics',      'author': 'David John'    },  ];
  bool? _isEditMode = false;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DataTable Demo'),
        ),
        body: ListView(
          children: [
            _createDataTable(),
            _createCheckboxField()
          ],
        ),
      ),
    );
  }
DataTable _createDataTable() {
    return DataTable(columns: _createColumns(), rows: _createRows());
  }
List<DataColumn> _createColumns() {
    return [      DataColumn(label: Text('ID')),      DataColumn(label: Text('Book')),      DataColumn(label: Text('Author'))    ];
  }
List<DataRow> _createRows() {
    return _books
        .map((book) => DataRow(cells: [
              DataCell(Text('#' + book['id'].toString())),
              _createTitleCell(book['title']),
              DataCell(Text(book['author']))
            ]))
        .toList();
  }
DataCell _createTitleCell(bookTitle) {
    return DataCell(_isEditMode == true ? 
            TextFormField(initialValue: bookTitle, 
            style: TextStyle(fontSize: 14)) 
            : Text(bookTitle));
  }
Row _createCheckboxField() {
    return Row(
      children: [
        Checkbox(
          value: _isEditMode,
          onChanged: (value) {
            setState(() {
              _isEditMode = value;
            });
          },
        ),
        Text('Edit mode'),
      ],
    );
  }
}

上面的代码用_createTitleCell 函数动态地显示书名单元格的数据。如果编辑模式复选框被选中,_createTitleCell 函数返回一个可编辑的文本框。否则,它像往常一样返回一个只读的文本字段。

新的应用程序将按以下方式工作。

Display dynamic content in your DataTable

设计模式指南

Flutter让程序员在Dart文件内定义他们的应用程序布局,它不像其他流行框架那样提供单独的布局语法。因此,当您开发大规模的Flutter应用程序时,您的源文件会变得很复杂,可读性也会降低。

这种情况也会发生在数据表上。下面的设计模式实践帮助我们降低Flutter应用程序的复杂性。

  • 用MVC(模型-视图-控制器)这样的模式将应用逻辑和布局相关的代码分开
  • 将您的小部件的创建代码分解成独立的Dart函数;例如,像我们之前创建的_createColumns 函数。
  • 将整个应用程序分解成更小的可重用的组件
  • 为UI组件创建多个Dart源文件

结语

正如我们在本教程中讨论的,您可以使用Flutter DataTable来显示表格数据。而且,通过在上述配置中的编写,可以使数据表格具有很强的互动性和用户友好性。此外,您可以通过编写一些额外的Dart代码,并根据您的要求在DataTable附近添加一些其他的Flutter部件,来为DataTable添加诸如搜索和过滤等功能。

DataTable适用于显示少量的数据记录。如果您需要显示许多数据记录,请考虑使用PaginatedDataTable

如果你想为小屏幕显示大的数据表格,请确保用SingleChildScrollView包裹你的数据表格,以处理水平溢出的问题。

The postFlutter's DataTable widget:显示数据的指南首次出现在LogRocket博客上。