Compose开发PDF阅读器:缩放后VerticalPager滚动卡顿求助
解决Compose PDF阅读器缩放后滚动卡顿问题
核心问题分析
当前实现的性能瓶颈主要集中在缩放逻辑作用域过大、Bitmap绘制未优化、频繁状态更新触发全局重绘这几点上,以下是针对性的修复方案:
1. 将缩放逻辑从Pager容器移至单个页面
把graphicsLayer的缩放、平移逻辑从整个VerticalPager移到单个页面组件内,避免让Pager的布局计算、触摸事件处理都基于放大后的超大尺寸,大幅降低性能开销。
修改后的ViewPager代码
@OptIn(ExperimentalPagerApi::class, ExperimentalFoundationApi::class) @Composable fun ViewPager( list: List<Pair<ImageBitmap, List<Fields>>>, minScale: Float = 1f, maxScale: Float = 3f, scrollEnabled: MutableState<Boolean>, isRotation: Boolean = false ) { var targetScale by remember { mutableStateOf(1f) } val focusManager = LocalFocusManager.current VerticalPager( modifier= Modifier .fillMaxSize() .background(Color.Blue) .combinedClickable( interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { focusManager.clearFocus() }, onDoubleClick = { targetScale = if (targetScale >= 2f) 1f else 3f scrollEnabled.value = targetScale == 1f } ), count = list.size, userScrollEnabled = scrollEnabled.value, flingBehavior = ScrollableDefaults.flingBehavior() ) { page -> val (bitmap, fields) = list[page] ZoomablePage( imageBitmap = bitmap, fields = fields, targetScale = targetScale, minScale = minScale, maxScale = maxScale, scrollEnabled = scrollEnabled, isRotation = isRotation, color = if (page % 2 == 0) Color.Yellow else Color.Cyan ) } }
新增ZoomablePage封装缩放逻辑
@Composable fun ZoomablePage( imageBitmap: ImageBitmap, fields: List<Fields>, targetScale: Float, minScale: Float, maxScale: Float, scrollEnabled: MutableState<Boolean>, isRotation: Boolean, color: Color ) { val scale = animateFloatAsState(targetValue = maxOf(minScale, minOf(maxScale, targetScale))) var rotationState by remember { mutableStateOf(0f) } var offsetX by remember { mutableStateOf(0f) } var offsetY by remember { mutableStateOf(0f) } val screenWidthPx = with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() } val screenHeightPx = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } Box( modifier = Modifier .fillMaxSize() .background(color) .graphicsLayer( optimizationFlags = GraphicsLayerOptimizeFlags.PREFER_OVERLAY, scaleX = scale.value, scaleY = scale.value, rotationZ = if (isRotation) rotationState else 0f, translationX = offsetX, translationY = offsetY ) .pointerInput(Unit) { forEachGesture { awaitPointerEventScope { awaitFirstDown() do { val event = awaitPointerEvent() val zoom = event.calculateZoom() val newScale = targetScale * zoom val offset = event.calculatePan() if (newScale <= 1) { offsetX = 0f offsetY = 0f scrollEnabled.value = true } else { scrollEnabled.value = false offsetX += offset.x offsetY += offset.y // 边界限制优化,避免内容超出屏幕 val scaledImageWidth = screenWidthPx * scale.value val scaledImageHeight = screenHeightPx * scale.value offsetX = offsetX.coerceIn(-(scaledImageWidth - screenWidthPx)/2, (scaledImageWidth - screenWidthPx)/2) offsetY = offsetY.coerceIn(-(scaledImageHeight - screenHeightPx)/2, (scaledImageHeight - screenHeightPx)/2) if (zoom != 1f) { rotationState += event.calculateRotation() } } } while (event.changes.any { it.pressed }) } } } ) { PageContent( modifier = Modifier.fillMaxSize(), imagePainter = BitmapPainter(imageBitmap), fields = fields ) } }
2. 优化Bitmap绘制与内存占用
直接使用原始Bitmap进行实时缩放会导致GPU处理大量冗余像素,建议:
- 提前根据屏幕尺寸和最大缩放比例生成预缩放的Bitmap,避免运行时动态缩放
- 加载Bitmap时指定采样率,减少内存占用:
// 示例:根据屏幕尺寸计算采样率 fun decodeSampledBitmapFromFile(path: String, reqWidth: Int, reqHeight: Int): Bitmap { val options = BitmapFactory.Options().apply { inJustDecodeBounds = true BitmapFactory.decodeFile(path, this) inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) inJustDecodeBounds = false } return BitmapFactory.decodeFile(path, options) } - 启用
graphicsLayer的PREFER_OVERLAY优化标记,提升硬件加速效率
3. 减少不必要的重组与重绘
- 移除代码中所有调试用的
Log.e输出,避免运行时性能损耗 - 简化自定义Layout的测量逻辑,避免重复计算:
@Composable fun Page( modifier: Modifier = Modifier, fields: List<Fields>, pageContent: @Composable () -> Unit, color: Color ) { Box(modifier = modifier.fillMaxSize().background(color)) { Layout(content = pageContent) { measurables, constraints -> val imagePlaceable = measurables.first().measure(constraints) val fieldPlaceables = measurables.drop(1).map { it.measure(constraints.copy(minWidth = 0, minHeight = 0)) } layout(imagePlaceable.width, imagePlaceable.height) { imagePlaceable.place(0, 0) fieldPlaceables.forEachIndexed { index, placeable -> val field = fields[index] placeable.placeRelative(field.x, field.y - 70) } } } } }
内容的提问来源于stack exchange,提问作者Ramesh Kumar
相关产品推荐
相关产品推荐

