android - 使用 PdfRenderer 在 Jetpack Compose 中创建 PDF 查看器

标签 android kotlin pdf android-jetpack-compose coil

我正在尝试使用 PdfRenderer 创建可组合的 PDF 查看器和 Coil用于将位图加载到 LazyColumn .
这是我到目前为止得到的:

@Composable
fun PdfViewer(
    modifier: Modifier = Modifier,
    uri: Uri,
    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp)
) {
    val loaderScope = rememberCoroutineScope()
    val renderer = remember(uri) {
        val input = ParcelFileDescriptor.open(uri.toFile(), ParcelFileDescriptor.MODE_READ_ONLY)
        PdfRenderer(input)
    }
    val context = LocalContext.current
    val mutex = remember { Mutex() }
    val imageLoader = LocalContext.current.imageLoader
    BoxWithConstraints(modifier = modifier.fillMaxWidth()) {
        val width = with(LocalDensity.current) { maxWidth.toPx() }.toInt()
        val height = (width * sqrt(2f)).toInt()
        LazyColumn(
            verticalArrangement = verticalArrangement
        ) {
            items(
                count = renderer.pageCount,
                key = { it }
            ) { index ->
                val cacheKey = MemoryCache.Key("$uri-$index")
                val bitmap = remember(uri, index) {
                    val cachedBitmap = imageLoader.memoryCache[cacheKey]
                    if (cachedBitmap != null) cachedBitmap else {
                        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
                        loaderScope.launch(Dispatchers.IO) {
                            mutex.withLock {
                                Timber.d("Loading $uri - page $index")
                                renderer.openPage(index).use {
                                    it.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                                }
                            }
                        }
                        bitmap
                    }
                }
                val request = ImageRequest.Builder(context)
                    .size(width, height)
                    .memoryCacheKey(cacheKey)
                    .data(bitmap)
                    .build()

                Image(
                    modifier = Modifier.background(Color.White).aspectRatio(1f / sqrt(2f)).fillMaxWidth(),
                    contentScale = ContentScale.Fit,
                    painter = rememberImagePainter(request),
                    contentDescription = "Page ${index + 1} of ${renderer.pageCount}"
                )
            }
        }
    }
}
这种工作,但是当第一次加载位图时,它不会显示在列表中,直到我滚动(即重绘之后)。我想利用LazyColumn的功能并且仅在 PDF 页面可见时才加载它们。
有没有更好的方法来实现这一目标?

最佳答案

我设法解决它如下:

@Composable
fun PdfViewer(
    modifier: Modifier = Modifier,
    uri: Uri,
    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp)
) {
    val rendererScope = rememberCoroutineScope()
    val mutex = remember { Mutex() }
    val renderer by produceState<PdfRenderer?>(null, uri) {
        rendererScope.launch(Dispatchers.IO) {
            val input = ParcelFileDescriptor.open(uri.toFile(), ParcelFileDescriptor.MODE_READ_ONLY)
            value = PdfRenderer(input)
        }
        awaitDispose {
            val currentRenderer = value
            rendererScope.launch(Dispatchers.IO) {
                mutex.withLock {
                    currentRenderer?.close()
                }
            }
        }
    }
    val context = LocalContext.current
    val imageLoader = LocalContext.current.imageLoader
    val imageLoadingScope = rememberCoroutineScope()
    BoxWithConstraints(modifier = modifier.fillMaxWidth()) {
        val width = with(LocalDensity.current) { maxWidth.toPx() }.toInt()
        val height = (width * sqrt(2f)).toInt()
        val pageCount by remember(renderer) { derivedStateOf { renderer?.pageCount ?: 0 } }
        LazyColumn(
            verticalArrangement = verticalArrangement
        ) {
            items(
                count = pageCount,
                key = { index -> "$uri-$index" }
            ) { index ->
                val cacheKey = MemoryCache.Key("$uri-$index")
                var bitmap by remember { mutableStateOf(imageLoader.memoryCache[cacheKey]) }
                if (bitmap == null) {
                    DisposableEffect(uri, index) {
                        val job = imageLoadingScope.launch(Dispatchers.IO) {
                            val destinationBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
                            mutex.withLock {
                                Timber.d("Loading PDF $uri - page $index/$pageCount")
                                if (!coroutineContext.isActive) return@launch
                                try {
                                    renderer?.let {
                                        it.openPage(index).use { page ->
                                            page.render(
                                                destinationBitmap,
                                                null,
                                                null,
                                                PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY
                                            )
                                        }
                                    }
                                } catch (e: Exception) {
                                    //Just catch and return in case the renderer is being closed
                                    return@launch
                                }
                            }
                            bitmap = destinationBitmap
                        }
                        onDispose {
                            job.cancel()
                        }
                    }
                    Box(modifier = Modifier.background(Color.White).aspectRatio(1f / sqrt(2f)).fillMaxWidth())
                } else {
                    val request = ImageRequest.Builder(context)
                        .size(width, height)
                        .memoryCacheKey(cacheKey)
                        .data(bitmap)
                        .build()

                    Image(
                        modifier = Modifier.background(Color.White).aspectRatio(1f / sqrt(2f)).fillMaxWidth(),
                        contentScale = ContentScale.Fit,
                        painter = rememberImagePainter(request),
                        contentDescription = "Page ${index + 1} of $pageCount"
                    )
                }
            }
        }
    }
}
这也应该处理 pdf 渲染器的处置。

关于android - 使用 PdfRenderer 在 Jetpack Compose 中创建 PDF 查看器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69943176/

相关文章:

php - 哪种类型的 ip 地址是 "2405:204:a581:e021:995b:368e:9b00:ddd5"?

android - Kotlin 崩溃无法将提供的符号转换为 Dependency 类型的对象 : org. jetbrains.kotlin.gradle.dsl.KotlinProjectExtension_Decorated

kotlin - 将 `if...else null` 写为 ?./的一部分的惯用 Kotlin 方式是什么? : chains?

c# - 如何使用 C# 将图像插入到 pdf 文档中?

Android Admob 无法加载广告 : 0 - Error waiting for future TimeoutException Problem with OREO only

java - ViewPostIme 指针 1/0

android 可滚动 TextView 在 ScrollView 中不起作用

android - 用于文本选择的 float 工具栏 Jetpack Compose

iphone - iOS 上的矢量 PDF 图形元素

html - 如何将 PDF 转换为 HTML?