Flutter表格SfDataGrid进阶使用

目录

分页

封装分页组件

[1. 创建数据源](#1. 创建数据源)

[2. 使用](#2. 使用)

[3. 效果演示](#3. 效果演示)

上拉刷新|下拉加载更多

[1. 修改请求逻辑](#1. 修改请求逻辑)

[2. 数据渲染](#2. 数据渲染)

[3. 效果演示](#3. 效果演示)

总结


在上一章中我们基本入门了SfDataGrid的使用,从无到有渲染数据表格,了解它的常见API、以及对表格的个性化和数据的操作,在本章中我们将继续探究SfDataGrid数据表格。

分页

SfDataGrid并没有为我们提供分页功能,但是我们可以自己封装一个分页组件,然后通过数据交互到达分页切换的效果

封装分页组件

import 'package:flutter/material.dart';

class Pagination extends StatefulWidget {
  // 总页数
  final int totalPages;
  // 当前页
  final int currentPage;
  // 切换页码回调函数
  final Function(int) onPageChanged;

  const Pagination({super.key,
    required this.totalPages,
    required this.currentPage,
    required this.onPageChanged
  });

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

class _PaginationState extends State<Pagination> {
  late int _currentPage;

  @override
  void initState() {
    super.initState();
    // 初始化当前页面 为外部传入的页码
    _currentPage = widget.currentPage;
  }

  // 修改页面回调函数
  void _changePage(int page) {
    // 如果 点击的页码 != 当前页码 & 点击的页码 >= 1 && 点击的页码达到最大页码 才会更新当前页码
    if (page != _currentPage && page >= 1 && page <= widget.totalPages) {
      setState(() {
        _currentPage = page;
      });
      // 触发回调,向外部传递 更新后的页码
      widget.onPageChanged(page);
    }
  }

  // 获取当前可展示的 页码,页码最多显示 9个
  List<int> _getVisiblePages() {
    // 可见页码 数组
    List<int> visiblePages = [];
    // 起始页码
    int startPage = _currentPage - 4;
    // 结束页码
    int endPage = _currentPage + 4;


    // 如果起始页码小于1
    if (startPage < 1) {
      // 重新计算 结束页码值
      endPage += (1 - startPage);
      startPage = 1;
    }

    // 如果结束页码 大于总页码
    if (endPage > widget.totalPages) {
      startPage -= (endPage - widget.totalPages);
      endPage = widget.totalPages;
    }

    // 防止 起始 | 结束 页码越界
    startPage = startPage < 1 ? 1 : startPage;
    endPage = endPage > widget.totalPages ? widget.totalPages : endPage;

    // 存储可见页码
    for (int i = startPage; i <= endPage; i++) {
      visiblePages.add(i);
    }

    return visiblePages;
  }

  @override
  Widget build(BuildContext context) {
    List<int> visiblePages = _getVisiblePages();
    return SizedBox(
      width: MediaQuery.of(context).size.width,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          IconButton(
            icon: const Icon(Icons.chevron_left),
            onPressed: _currentPage > 1 ? () => _changePage(_currentPage - 1) : null,
          ),
          ...visiblePages.map((page) {
            // 标识是否为当前页
            bool flag = page == _currentPage;
            return GestureDetector(
              onTap: () {
                _changePage(page);
              },
              child: Container(
                width: 25,
                alignment: Alignment.center,
                decoration: BoxDecoration(
                  color: flag ? Colors.blue : Colors.transparent,
                  borderRadius: BorderRadius.circular(35)
                ),
                child: Text(
                  page.toString(),
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: flag ? Colors.white : Colors.black,
                  ),
                ),
              ),
            );
          }),
          IconButton(
            icon: const Icon(Icons.chevron_right),
            onPressed: _currentPage < widget.totalPages ? () => _changePage(_currentPage + 1) : null,
          ),
        ],
      ),
    );
  }
}

实现效果

1. 创建数据源

在创建数据源中,我们使用Future模拟网络请求数据

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:table_demo/book.dart';

class BookTableSource extends DataGridSource {
  // 每页数量
  int _pageSize = 10;

  // 页码
  int _currentPage = 1;

  // 标识数据是否正在加载
  bool _isLoading = false;
  // 表格数据
  List<Book> books = [];

  BookDataSource() {
    _loadData();
  }

  void _loadData() {
    if (_isLoading) return;
    _isLoading = true;

    // 模拟从服务器或数据库加载数据
    Future.delayed(const Duration(seconds: 2), () {
      final newBooks = List.generate(
        _pageSize,
        (index) => Book(
          id: (_currentPage - 1) * _pageSize + index + 1,
          bookName: '书籍 ${(_currentPage - 1) * _pageSize + index + 1}',
          author: '作者 ${(_currentPage - 1) * _pageSize + index + 1}',
          price: 10.0 + index,
          publicTime: '时间 ${(_currentPage - 1) * _pageSize + index + 1}',
        ),
      );
      books = newBooks;
      _isLoading = false;
      notifyListeners();
    });
  }

  @override
  List<DataGridRow> get rows => books.map<DataGridRow>((book) {
    return DataGridRow(cells: [
      DataGridCell(columnName: "id", value: book.id),
      DataGridCell(columnName: "bookName", value: book.bookName),
      DataGridCell(columnName: "author", value: book.author),
      DataGridCell(columnName: "price", value: book.price),
      DataGridCell(columnName: "publicTime", value: book.publicTime),
    ]);
  }).toList();

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    // 具体的渲染
    return DataGridRowAdapter(
        cells: row.getCells().map<Widget>((cell) {
      return Container(
        alignment: Alignment.center,
        decoration: BoxDecoration(
          color: Colors.white10,
          // 添加边框
          border: Border.all(color: Colors.grey),
        ),
        child: Text(
          cell.value.toString(),
          // 文字样式
          style: const TextStyle(fontSize: 14, color: Colors.black87),
        ),
      );
    }).toList());
  }

  void loadPage(int page) {
    _currentPage = page;
    _loadData();
  }
}

2. 使用

联合SfDataGrid使用,布局为表格在上面,分页在下方,在切换页码时,会触发请求数据的方法,然后返回对应的数据

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late BookTableSource _bookDataSource;
  int currPage = 1;

  @override
  void initState() {
    super.initState();
    _bookDataSource = BookTableSource();
    _bookDataSource.loadPage(currPage);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("表格"),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            SfDataGrid(
                // 数据源
                source: _bookDataSource,
                // 表格列:注意 数据源的 列数 一定要与 表格列数 一致
                columns: [
                  GridColumn(
                      // 字段名(field):与数据源的字段名要保持一致
                      columnName: "id",
                      // 字段名称(label)
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            'id',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "bookName",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '名称',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "author",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '作者',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "price",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '价格',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "publicTime",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '发布时间',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                ]),
            Pagination(
              totalPages: 20,
              currentPage: currPage,
              onPageChanged: (page) {
                currPage = page;
                _bookDataSource.loadPage(page);
                setState(() {});
              },
            ),
          ],
        ),
      ),
    );
  }
}

3. 效果演示

上拉刷新|下拉加载更多

1. 修改请求逻辑

要实现这个功能,要把之前的分页请求逻辑稍微修改一下,需要知道当前操作是刷新还是加载更多,故需要请求方法新增参数isLoadMore:true为加载更多,false为刷新

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:table_demo/book.dart';

class BookTableSource extends DataGridSource {
  // 每页数量
  int _pageSize = 10;
  // 页码
  int _currentPage = 0;
  // 标识是否在请求数据
  bool _isLoading = false;
  // 表格数据
  List<Book> books = [];

  bool get isLoading => _isLoading;

  /// page:请求页码 isLoadMore:是否加载更多,控制要合并新数据还是替换,true为合并,false替换
  Future<void> loadData({bool? isLoadMore = true}) async {
    if (_isLoading) return;
    _isLoading = true;
    _currentPage = isLoadMore == true ? ++_currentPage : 1;

    // 模拟从服务器或数据库加载数据
    Future.delayed(const Duration(seconds: 2), () {
      // 根据 当前页码 和 每页数量 生成新数据
      final newBooks = List.generate(
        _pageSize,
        (index) => Book(
          id: (_currentPage - 1) * _pageSize + index + 1,
          bookName: '书籍 ${(_currentPage - 1) * _pageSize + index + 1}',
          author: '作者 ${(_currentPage - 1) * _pageSize + index + 1}',
          price: 10.0 + index,
          publicTime: '时间 ${(_currentPage - 1) * _pageSize + index + 1}',
        ),
      );
      // 合并新数据
      books = isLoadMore == true ? [...books, ...newBooks] : newBooks;
      _isLoading = false;
      notifyListeners();
    });
  }

  @override
  List<DataGridRow> get rows => books.map<DataGridRow>((book) {
    return DataGridRow(cells: [
      DataGridCell(columnName: "id", value: book.id),
      DataGridCell(columnName: "bookName", value: book.bookName),
      DataGridCell(columnName: "author", value: book.author),
      DataGridCell(columnName: "price", value: book.price),
      DataGridCell(columnName: "publicTime", value: book.publicTime),
    ]);
  }).toList();

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    // 具体的渲染
    return DataGridRowAdapter(
        cells: row.getCells().map<Widget>((cell) {
      return Container(
        alignment: Alignment.center,
        decoration: BoxDecoration(
          color: Colors.white10,
          // 添加边框
          border: Border.all(color: Colors.grey),
        ),
        child: Text(
          cell.value.toString(),
          // 文字样式
          style: const TextStyle(fontSize: 14, color: Colors.black87),
        ),
      );
    }).toList());
  }
}

2. 数据渲染

在修改完数据源之后,再来修改表格的渲染,为表格新增属性verticalScrollController,通过它可以为表格添加滚动事件,了解表格是否滚动到底部或者滚动到顶部,具体代码如下

class _BookTablePageState extends State<BookTablePage> {
  late BookTableSource _bookDataSource;
  ScrollController controller = ScrollController();

  @override
  void initState() {
    super.initState();
    _bookDataSource = BookTableSource();
    // 初始化加载数据
    _bookDataSource.loadData();
    // 为表格添加滚动事件
    controller.addListener(onScroll);
  }

  // 表格滚动事件
  void onScroll() {
    // 判断是否滚动到顶部
    if (controller.position.atEdge && controller.position.pixels == 0) {
      // 请求数据
      _bookDataSource.loadData(isLoadMore: false);
    } else if (controller.position.pixels ==
            controller.position.maxScrollExtent &&
        !_bookDataSource.isLoading) {// 如果表格滚动到底部 且 未处在表格加载状态
      // 记载更多数据
      _bookDataSource.loadData();
    }
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("表格"),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            SfDataGrid(
                // 数据源
                source: _bookDataSource,
                // 竖向滚动控制器
                verticalScrollController: controller,
                // 定义表格底部组件:数据在加载中展示 加载组件,反之不展示任何内容
                footer: _bookDataSource.isLoading
                    ? Container(
                        alignment: Alignment.center,
                        child: const CircularProgressIndicator(),
                      )
                    : Container(),
                // 表格列:注意 数据源的 列数 一定要与 表格列数 一致
                columns: [
                  GridColumn(
                      // 字段名(field):与数据源的字段名要保持一致
                      columnName: "id",
                      // 字段名称(label)
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            'id',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "bookName",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '名称',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "author",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '作者',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "price",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '价格',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                  GridColumn(
                      columnName: "publicTime",
                      label: Container(
                          padding: const EdgeInsets.all(16.0),
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            border: Border.all(color: Colors.grey),
                          ),
                          child: const Text(
                            '发布时间',
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ))),
                ]),
          ],
        ),
      ),
    );
  }
}

3. 效果演示

总结

该插件还有更多的功能值得探索,这里笔者只是整理了日常开发中遇到的一些需求,如果有什么不对的,欢迎评论区指正哈!谢谢

相关推荐
Qin_jiangshan2 小时前
使用HBuilderX 进行uniapp 打包Android APK
android·uni-app
jzlhll1233 小时前
android编译assets集成某文件太大更新导致git仓库变大
android
高林雨露10 小时前
ImageView android:scaleType各种属性
android·imageview各种属性
Hi-Dison10 小时前
OpenHarmony系统中实现Android虚拟化、模拟器相关的功能,包括桌面显示,详细解决方案
android
事业运财运爆棚12 小时前
http 502 和 504 的区别
android
峥嵘life13 小时前
Android Studio新版本的一个资源id无法找到的bug解决
android·bug·android studio
编程乐学13 小时前
网络资源模板--Android Studio 实现绿豆通讯录
android·前端·毕业设计·android studio·大作业·安卓课设·绿豆通讯录
朴拙数科17 小时前
mysql报错解决 `1525 - Incorrect DATETIME value: ‘0000-00-00 00:00:00‘`
android·数据库·mysql
nicepainkiller17 小时前
Flutter 内嵌 unity3d for android
flutter·unity3d
恋猫de小郭17 小时前
Flutter Web 正式移除 HTML renderer,只支持 CanvasKit 和 SkWasm
前端·flutter·html