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
相关产品推荐
相关产品推荐

