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

Flutter多页PDF打印时如何在页脚显示每页合计值

Flutter PDF跨页表格单页合计页脚实现方案

核心思路:放弃依赖组件自动分页的回调能力,提前完成分页切分与单页数据统计,逐页独立渲染内容,从根源上避免自动分页后无法获取当前页数据范围、合计值计算不准的问题。

依赖准备

使用Flutter生态主流的PDF生成与打印套件,在pubspec.yaml中添加依赖:

dependencies:
  pdf: ^3.10.7
  printing: ^5.11.1

具体实现步骤

  • 提前固定/测算所有元素高度
    先明确PDF页面规格、上下左右边距,再分别测算固定元素的高度:包括表格表头、单页合计行、底部页脚的高度。用页面总可用高度减去上述固定元素的高度,得到单页可承载的表格数据区最大高度。

    若存在单元格文本自动换行、内容高度不固定的场景,需要提前根据文本长度、单元格宽度、字体样式计算每一条数据行的实际渲染高度,不要硬编码行高,避免分页错位、内容溢出。

  • 手动切分分页数据并计算单页合计
    遍历全量表格数据,逐行累加行高,同时累加当前批次的统计值(如金额总和)。当累加的行高超过单页数据区最大高度时,将当前累积的行集合、对应统计值封存为单页数据源,重置累加计数器后继续处理剩余数据,直到所有数据完成切分。
    切分过程中如果遇到跨多行的合并单元格,需要判断合并范围是否跨过当前分页切点,如果跨过则将切点前移,保证整个合并单元格落在同一页内,避免单元格被截断。
  • 逐页渲染完整页面内容
    每一页作为独立的Page组件渲染,从上到下按顺序排布元素:
    1. 固定表格表头
    2. 当前页切分好的表格数据片段
    3. 单页合计行,直接填入切分阶段算好的当前页统计值
    4. 底部固定页脚,可填充页码、打印时间等通用信息
      如果需要在最后一页展示全表总合计,只需在最后一页的单页合计行下方追加全量统计值即可,不影响前面页面的渲染逻辑。

常见避坑

  • 不要直接用MultiPage组件的自动表格分页加footer回调实现该需求:该回调只能获取当前页的渲染上下文,无法拿到当前页实际渲染了哪些数据行,根本无法准确计算单页合计,最终会出现合计值和页内数据不匹配的问题。
  • 高度测算阶段的字体、边距、样式参数必须和实际渲染时的参数完全一致,否则会出现分页切点偏移,导致某行内容被拆分到两页的问题。

核心代码示例

// 初始化页面基础参数
const pageMargin = EdgeInsets.symmetric(horizontal: 20, vertical: 30);
const headerHeight = 40.0;
const pageTotalRowHeight = 35.0;
const footerHeight = 20.0;
final singlePageDataMaxHeight = PdfPageFormat.a4.availableHeight 
  - pageMargin.vertical 
  - headerHeight 
  - pageTotalRowHeight 
  - footerHeight;

// 分页切分与单页合计计算
class SinglePageData {
  final List<InvoiceRow> rows;
  final double totalAmount;
  SinglePageData({required this.rows, required this.totalAmount});
}

List<SinglePageData> pageList = [];
double currentRowHeightAccumulate = 0;
List<InvoiceRow> currentPageRows = [];
double currentPageTotal = 0;

for (final row in allInvoiceRows) {
  final rowRenderHeight = _calcRowActualHeight(row); // 自定义方法,计算单行实际渲染高度
  // 超出单页容量时封存当前页
  if (currentRowHeightAccumulate + rowRenderHeight > singlePageDataMaxHeight) {
    pageList.add(SinglePageData(rows: currentPageRows, totalAmount: currentPageTotal));
    currentRowHeightAccumulate = 0;
    currentPageRows = [];
    currentPageTotal = 0;
  }
  currentPageRows.add(row);
  currentRowHeightAccumulate += rowRenderHeight;
  currentPageTotal += row.amount;
}
// 补入最后一页数据
if (currentPageRows.isNotEmpty) {
  pageList.add(SinglePageData(rows: currentPageRows, totalAmount: currentPageTotal));
}

// 逐页生成PDF文档
final pdfDoc = Document();
for (var pageIndex = 0; pageIndex < pageList.length; pageIndex++) {
  final pageData = pageList[pageIndex];
  pdfDoc.addPage(
    Page(
      margin: pageMargin,
      build: (context) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 表头
            SizedBox(height: headerHeight, child: const InvoiceTableHeader()),
            // 当前页表格数据
            Expanded(
              child: Table(
                border: TableBorder.all(width: 0.5),
                children: pageData.rows.map((e) => _buildDataRow(e)).toList(),
              )
            ),
            // 本页合计行
            SizedBox(
              height: pageTotalRowHeight,
              child: Align(
                alignment: Alignment.centerRight,
                child: Text("本页金额合计:¥${pageData.totalAmount.toStringAsFixed(2)}",
                  style: TextStyle(fontWeight: FontWeight.bold)
                )
              )
            ),
            // 页脚
            SizedBox(
              height: footerHeight,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text("打印时间:${DateTime.now().toString().substring(0,19)}"),
                  Text("第${pageIndex+1}页 / 共${pageList.length}页")
                ]
              )
            )
          ]
        );
      }
    )
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:30:45