美文网首页
Android Jetpack compose 版本的 Phot

Android Jetpack compose 版本的 Phot

作者: 雁过留声_泪落无痕 | 来源:发表于2022-10-31 09:25 被阅读0次

    Android Jetpack compose 下的简版 PhotoView

    可双击放大、还原,可缩放、平移

    /**
     * 放大倍数 a: 2倍, 放大倍数 b: 容器宽/图片宽、容器长/图片长的较大值.
     * 双击: 放大到倍数 min(a,b), 缩小到原始尺寸
     * 双指: 放大到倍数 max(a,b), 缩小到原始尺寸
     */
    @Composable
    fun PhotoView(click: (() -> Unit)? = null) {
        var imageInitialSize by remember {
            mutableStateOf(IntSize.Zero)
        }
        var boxSize by remember {
            mutableStateOf(IntSize.Zero)
        }
    
        var maxScale by remember {
            mutableStateOf(1f)
        }
        var scale by remember {
            mutableStateOf(1f)
        }
        var offset by remember {
            mutableStateOf(Offset.Zero)
        }
        // 使用 transformable 进行缩放、平移和旋转, 但是都需要双指, 不太适用图片放大后的平移(通常是单指)
        val transformState = rememberTransformableState { zoomChange, panChange, rotationChange ->
            scale = (scale * zoomChange).coerceIn(1f, maxScale)
    
            // 1. 直接使用平移距离
            // offset += panChange
    
            // 2. 考虑图片超出容器边界问题
            /*var x = offset.x
            if (scale * imageInitialSize.width > boxSize.width) {
                val delta = scale * imageInitialSize.width - boxSize.width
                x = (offset.x + panChange.x).coerceIn(-delta / 2, delta / 2)
            }
            var y = offset.y
            if (scale * imageInitialSize.height > boxSize.height) {
                val delta = scale * imageInitialSize.height - boxSize.height
                y = (offset.y + panChange.y).coerceIn(-delta / 2, delta / 2)
            }
            offset = Offset(x, y)*/
        }
    
        Box(modifier = Modifier
            .fillMaxSize()
            .graphicsLayer {
                scaleX = scale
                scaleY = scale
                translationX = offset.x
                translationY = offset.y
            }
            // 1. 使用 transformable 处理缩放和平移
            //.transformable(transformState)
            // 2. 使用 awaitPointerEventScope 自行处理缩放和平移
            .pointerInput(Unit) {
                forEachGesture {
                    awaitPointerEventScope {
                        do {
                            val event = awaitPointerEvent()
                            val zoomChange = event.calculateZoom()
                            // LogUtils.d("zoomChange: $zoomChange")
                            scale *= zoomChange
    
                            var panChange = event.calculatePan()
                            // LogUtils.d("panChange: $panChange")
                            // 乘以 scale 防止在图片放大状态下移动得特别慢
                            panChange *= scale
    
                            // 为了让放大倍数超过 maxScale 时还可以随手势继续放大,
                            // 但是要手松开后回到 maxScale 这个状态, 在下面 awaitPointerEventScope 结束后
                            // 执行了 scale.coerceIn(1f, maxScale).
                            // 因此这个地方也要临时使用最后的真实值来计算 offset, 否则会出现整个图片上下 offset
                            // 不一致的情况(放大到超过 maxScale 后再松手)
                            val tempScale = scale.coerceIn(1f, maxScale)
                            var x = offset.x
                            if (tempScale * imageInitialSize.width > boxSize.width) {
                                val delta = tempScale * imageInitialSize.width - boxSize.width
                                x = (offset.x + panChange.x).coerceIn(-delta / 2, delta / 2)
                            }
                            var y = offset.y
                            if (tempScale * imageInitialSize.height > boxSize.height) {
                                val delta = tempScale * imageInitialSize.height - boxSize.height
                                y = (offset.y + panChange.y).coerceIn(-delta / 2, delta / 2)
                            }
    
                            if (x != offset.x || y != offset.y) {
                                offset = Offset(x, y)
                            }
    
                            event.changes.forEach {
                                if (it.positionChanged()) {
                                    it.consume()
                                }
                            }
                        } while (event.changes.any { it.pressed })
                    }
    
                    scale = scale.coerceIn(1f, maxScale)
                    if (scale == 1f) {
                        offset = Offset.Zero
                    }
                }
            }
            .pointerInput(Unit) {
                detectTapGestures(
                    onDoubleTap = {
                        offset = Offset.Zero
                        if (scale != 1.0f) {
                            scale = 1.0f
                        } else {
                            scale = min(2f, maxScale)
                        }
                    }, onTap = {
                        click?.invoke()
                    }
                )
            }
            .onSizeChanged {
                // LogUtils.d("Box onSizeChanged")
                boxSize = it
                val xRatio = it.width.toFloat() / imageInitialSize.width
                val yRatio = it.height.toFloat() / imageInitialSize.height
                maxScale = max(2f, max(xRatio, yRatio))
            },
            contentAlignment = Alignment.Center
        ) {
            Image(
                modifier = Modifier.onSizeChanged {
                    // LogUtils.d("Image onSizeChanged")
                    imageInitialSize = it
                }, painter = painterResource(id = R.drawable.screenshot), contentDescription = ""
            )
        }
    }
    
    @Preview(showBackground = true)
    @Composable
    fun PhotoViewPreview() {
        PhotoView()
    }
    
    使用示例.gif

    相关文章

      网友评论

          本文标题:Android Jetpack compose 版本的 Phot

          本文链接:https://www.haomeiwen.com/subject/dnrrtdtx.html