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

Kotlin下使用Android PdfDocument实现多页PDF生成的方法咨询

Android PdfDocument 多页PDF生成解决方案

核心思路是预先划定每页的可绘制区域,渲染每一行数据前判断是否触发分页阈值,触发则创建新页并重绘页眉、表头后继续渲染内容,具体实现如下:

关键调整点

  • 新增页面底部边距配置,计算得出每页内容的最大Y轴阈值,避免内容超出页面
  • 遍历表格数据时实时校验当前Y坐标,超过阈值则执行分页逻辑
  • 分页时先结束当前页,再创建新的页面对象,重新绘制公共头部、表格表头后重置内容起始Y坐标

优化后完整代码

object PDFGenerator {
    private const val TAG = "PDFGenerator"
    var pageHeight = 1120
    var pageWidth = 792 // 修正原变量名拼写错误

    var logoHeight = 50
    var logoWidth = 50
    // 新增底部边距配置,可按需调整
    private const val PAGE_BOTTOM_MARGIN = 80F
    // 固定行高,也可后续通过Paint动态计算
    private const val ROW_HEIGHT = 30F

    fun generatePDF(
        context: Context,
        headerBuilder: HeaderBuilder,
        outputFileName: String = "GeneratedPDF",
        logo: Bitmap,
        tableTitle: TableTitleBuilder,
        valueList: MutableList<TableValuesBuilder>
    ) {
        val pdfDocument = PdfDocument()
        Log.d(TAG, "generatePDF: starting")

        val paint = Paint()
        val titlePaint = Paint()
        var pageNumber = 1
        // 创建首页
        var pageInfo = PageInfo.Builder(pageWidth, pageHeight, pageNumber).create()
        var currentPage = pdfDocument.startPage(pageInfo)
        var canvas: Canvas = currentPage.canvas
        val scaledLogo = Bitmap.createScaledBitmap(logo, logoWidth, logoHeight, false)

        // 抽取公共绘制方法:每页的头部和表头
        fun drawPageHeaderAndTableHeader() {
            canvas.drawBitmap(scaledLogo, 56F, 40F, paint)
            // 绘制PDF Header
            titlePaint.textSize = 26F
            titlePaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
            titlePaint.color = ContextCompat.getColor(context, R.color.brandTitle)
            canvas.drawText(headerBuilder.title, 120F, 65F, titlePaint)
            titlePaint.textSize = 18F
            titlePaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
            titlePaint.color = ContextCompat.getColor(context, R.color.brandSubTitle)
            canvas.drawText(headerBuilder.subTitle, 120F, 85F, titlePaint)
            // 绘制表格表头
            var xTitlePos = 50F
            val yTitlePos = 150F
            titlePaint.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
            titlePaint.color = ContextCompat.getColor(context, R.color.titleText)
            titlePaint.textSize = 25F
            canvas.drawText(tableTitle.name1, xTitlePos, yTitlePos, titlePaint)
            xTitlePos += 200F
            canvas.drawText(tableTitle.name2, xTitlePos, yTitlePos, titlePaint)
            xTitlePos += 200F
            canvas.drawText(tableTitle.name3, xTitlePos, yTitlePos, titlePaint)
            xTitlePos += 200F
            canvas.drawText(tableTitle.name4, xTitlePos, yTitlePos, titlePaint)
        }

        // 先绘制首页的头部和表头
        drawPageHeaderAndTableHeader()

        // 绘制表格内容
        var xValuePos = 50F
        var yValuePos = 185F
        val maxDrawY = pageHeight - PAGE_BOTTOM_MARGIN
        titlePaint.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
        titlePaint.color = ContextCompat.getColor(context, R.color.valueText)
        titlePaint.textSize = 25F

        valueList.forEach { item ->
            // 绘制前判断是否需要分页
            if (yValuePos + ROW_HEIGHT > maxDrawY) {
                // 结束当前页
                pdfDocument.finishPage(currentPage)
                // 创建新页
                pageNumber++
                pageInfo = PageInfo.Builder(pageWidth, pageHeight, pageNumber).create()
                currentPage = pdfDocument.startPage(pageInfo)
                canvas = currentPage.canvas
                // 新页重绘头部和表头
                drawPageHeaderAndTableHeader()
                // 重置内容起始Y坐标
                yValuePos = 185F
            }
            // 绘制当前行数据
            canvas.drawText(item.value1, xValuePos, yValuePos, titlePaint)
            xValuePos += 200F
            canvas.drawText(item.value2, xValuePos, yValuePos, titlePaint)
            xValuePos += 200F
            canvas.drawText(item.value3, xValuePos, yValuePos, titlePaint)
            xValuePos += 200F
            canvas.drawText(item.value4, xValuePos, yValuePos, titlePaint)
            // 重置X坐标,Y坐标累加行高
            xValuePos = 50F
            yValuePos += ROW_HEIGHT
        }

        // 结束最后一页
        pdfDocument.finishPage(currentPage)
        // 回收Bitmap避免内存泄漏
        scaledLogo.recycle()

        val file = File(Environment.getExternalStorageDirectory(), "$outputFileName.pdf")
        Log.d(TAG, "generatePDF: $file")
        try {
            pdfDocument.writeTo(FileOutputStream(file))
            Toast.makeText(context, "PDF file generated successfully.", Toast.LENGTH_SHORT).show()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        pdfDocument.close()
        Log.d(TAG, "generatePDF: ending")
    }
}

可选优化点

  • 如果后续需要适配不同字号的行高,可以通过titlePaint.fontMetrics.let { it.descent - it.ascent + it.leading }计算实际行高替换固定的30F行高
  • 可在每页底部边距位置增加页码绘制逻辑,提升PDF可读性
  • 如果涉及长文本自动换行,可通过StaticLayout实现多行文本绘制后动态累加Y坐标

内容的提问来源于stack exchange,提问作者Devid John

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:09:02