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

如何将带滚动内容的@Composable完整转为PDF(含非屏幕内容)

解决Compose长滚动内容完整转Bitmap并生成PDF的问题

你当前使用的capturable库只能捕获屏幕可见区域的内容,原因是它基于当前渲染的可见Compose视图进行截图,超出屏幕的部分没有被布局渲染,自然无法捕获。要解决这个问题,需要直接完整绘制整个Composable的内容到画布,再转为Bitmap,具体方案如下:

一、完整捕获长滚动Composable为Bitmap

通过PictureRecorder记录Composable的全部绘制指令,不管内容是否在屏幕内,步骤如下:

  1. 先测量Composable的真实完整尺寸
  2. 创建足够大的画布让Composable完整绘制
  3. 将绘制记录转为Bitmap

实现代码

@Composable
fun captureFullComposableToBitmap(
    composable: @Composable () -> Unit,
    onBitmapCaptured: (Bitmap) -> Unit
) {
    val layoutInfo = remember { mutableStateOf<LayoutInfo?>(null) }

    // 测量整个Composable的真实宽高
    Box(
        modifier = Modifier
            .fillMaxSize()
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
                layoutInfo.value = LayoutInfo(placeable.width, placeable.height)
                layout(placeable.width, placeable.height) {
                    placeable.place(0, 0)
                }
            }
    ) {
        composable()
    }

    LaunchedEffect(layoutInfo.value) {
        layoutInfo.value?.let { info ->
            // 初始化PictureRecorder记录绘制内容
            val pictureRecorder = PictureRecorder()
            val canvas = Canvas(pictureRecorder.beginRecording(info.width, info.height))
            val context = LocalContext.current

            // 创建ComposeView并让其完整绘制到画布
            val composeView = ComposeView(context).apply {
                setContent { composable() }
                measure(
                    MeasureSpec.makeMeasureSpec(info.width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(info.height, MeasureSpec.EXACTLY)
                )
                layout(0, 0, info.width, info.height)
                draw(canvas)
            }

            // 将绘制记录转为Bitmap
            val bitmap = Bitmap.createBitmap(info.width, info.height, Bitmap.Config.ARGB_8888)
            val bitmapCanvas = Canvas(bitmap)
            pictureRecorder.endRecording().draw(bitmapCanvas)
            onBitmapCaptured(bitmap)
        }
    }
}

// 辅助类存储布局宽高
data class LayoutInfo(val width: Int, val height: Int)

二、优化PDF生成(适配长内容分页)

如果生成的Bitmap高度过大,直接作为单页PDF会导致内容压缩或超出页面范围,建议拆分Bitmap为多页PDF:

实现代码

fun createPdfFromLongBitmap(bitmap: Bitmap, outputPath: String, context: Context) {
    val pdfWriter = PdfWriter(outputPath)
    val pdfDoc = PdfDocument(pdfWriter)
    val document = Document(pdfDoc, PageSize.A4)
    val pageWidth = document.pageSize.width - document.leftMargin - document.rightMargin
    val pageHeight = document.pageSize.height - document.topMargin - document.bottomMargin

    val bitmapWidth = bitmap.width.toFloat()
    val bitmapHeight = bitmap.height.toFloat()
    val scale = pageWidth / bitmapWidth
    val scaledHeight = bitmapHeight * scale

    var currentY = 0f
    while (currentY < scaledHeight) {
        val page = pdfDoc.addNewPage()
        val canvas = Canvas(page)
        document.setMargins(0f, 0f, 0f, 0f)

        // 计算当前页需要截取的Bitmap区域
        val srcRect = RectF(
            0f,
            currentY / scale,
            bitmapWidth,
            min((currentY + pageHeight) / scale, bitmapHeight)
        )
        val destRect = RectF(0f, 0f, pageWidth, pageHeight)

        canvas.drawBitmap(bitmap, srcRect, destRect, null)
        currentY += pageHeight
    }

    document.close()
    shareFile(context, outputPath)
}

三、使用示例

在你的Composable中调用上述方法即可:

val context = LocalContext.current

// 定义你的长滚动内容
val longScrollableContent = @Composable {
    LazyColumn(modifier = Modifier.fillMaxWidth()) {
        items(100) { index ->
            Column(modifier = Modifier.padding(16.dp)) {
                Text(text = "条目 $index", fontSize = 18.sp)
                Divider(modifier = Modifier.padding(top = 8.dp))
            }
        }
    }
}

Button(onClick = {
    captureFullComposableToBitmap(longScrollableContent) { bitmap ->
        val pdfPath = "${context.externalCacheDir}/full_content.pdf"
        createPdfFromLongBitmap(bitmap, pdfPath, context)
    }
}) {
    Text("生成完整PDF")
}

关键说明

  • 该方案不依赖屏幕可见区域,直接通过绘制指令记录完整内容,能捕获所有滚动区域的内容
  • 测量步骤是核心,确保画布尺寸匹配Composable的真实完整大小
  • 分页处理可以保证PDF的可读性,避免长图被压缩变形

内容的提问来源于stack exchange,提问作者Yarik Nikolenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 03:37:54