I'm trying to create a PDF viewer composable using the PdfRenderer and Coil for loading the bitmaps into a LazyColumn
.
This is what I got so far:
@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}"
)
}
}
}
}
This kind of works, however when the bitmap is first loaded, it won't display in the list until I scroll (i.e. after a redraw). I want to make use of the features of LazyColumn
and only load the PDF pages when they become visible.
Is there any better way to achieve this?