如何使用 detectTransformGestures 但不消耗所有指针事件

How to use detectTransformGestures but not consuming all pointer event

我正在制作一个全屏照片查看器,其中包含一个寻呼机(使用 HorizontalPager)和每个页面,用户可以缩放 in/out 和平移图像,但仍然可以滑动页面。

我的想法是当图像未放大(比例因子 = 1)时会发生滑动页面,如果放大(比例因子 > 1)则 dragging/swiping 将左右平移图像。

这是包含我自定义的可缩放图像的 HorizontalPager 的代码:

@ExperimentalPagerApi
@Composable
fun ViewPagerSlider(pagerState: PagerState, urls: List<String>) {


var scale = remember {
    mutableStateOf(1f)
}
var transX = remember {
    mutableStateOf(0f)
}
var transY = remember {
    mutableStateOf(0f)
}

HorizontalPager(
    count = urls.size,
    state = pagerState,
    modifier = Modifier
        .padding(0.dp, 40.dp, 0.dp, 40.dp),
) { page ->

    Image(
        painter = rememberImagePainter(
            data = urls[page],
            emptyPlaceholder = R.drawable.img_default_post,
        ),
        contentScale = ContentScale.FillHeight,
        contentDescription = null,
        modifier = Modifier
            .fillMaxSize()
            .graphicsLayer(
                translationX = transX.value,
                translationY = transY.value,
                scaleX = scale.value,
                scaleY = scale.value,
            )
            .pointerInput(scale.value) {
                detectTransformGestures { _, pan, zoom, _ ->
                    scale.value = when {
                        scale.value < 1f -> 1f
                        scale.value > 3f -> 3f
                        else -> scale.value * zoom
                    }
                    if (scale.value > 1f) {
                        transX.value = transX.value + (pan.x * scale.value)
                        transY.value = transY.value + (pan.y * scale.value)
                    } else {
                        transX.value = 0f
                        transY.value = 0f
                    }
                }
            }
    )
}
}

所以我的图片最大放大3f,缩小不能小于0。

如果 detectTransformGestures 在我的代码中,我无法滑动以更改到另一个页面。如果我根据因素放置 detectTransformGestures(scale = 1,如果不放大,使其可滑动到另一个页面),那么它将是一个“死锁”,因为我无法放大,因为没有听众。

不知道有没有什么方法可以实现...

谢谢大家的宝贵时间!

如果您可以创建一个可变状态变量来跟踪缩放因子,则可以在缩放因子大于 1 时添加 pointerInput 修饰符,并在大于 1 时将其删除。像这样:

var zoomFactorGreaterThanOne by remember { mutableStateOf(false) }

Image(
    painter = rememberImagePainter(
        data = urls[page],
        emptyPlaceholder = R.drawable.img_default_post,
    ),
    contentScale = ContentScale.FillHeight,
    contentDescription = null,
    modifier = Modifier
        .fillMaxSize()
        .graphicsLayer(
            translationX = transX.value,
            translationY = transY.value,
            scaleX = scale.value,
            scaleY = scale.value,
        )
        .run {
            if (zoomFactorGreaterThanOne != 1.0f) {
                this.pointerInput(scale.value) {
                    detectTransformGestures { _, pan, zoom, _ ->
                        zoomFactorGreaterThanOne = scale.value > 1
                        
                        scale.value = when {
                            scale.value < 1f -> 1f
                            scale.value > 3f -> 3f
                            else -> scale.value * zoom
                        }
                        if (scale.value > 1f) {
                            transX.value = transX.value + (pan.x * scale.value)
                            transY.value = transY.value + (pan.y * scale.value)
                        } else {
                            transX.value = 0f
                            transY.value = 0f
                        }
                    }
                }
            } else {
                this
            }
        }

)

我不得不做类似的事情,想出了这个:

private fun ZoomableImage(
    modifier: Modifier = Modifier,
    bitmap: ImageBitmap,
    maxScale: Float = 1f,
    minScale: Float = 3f,
    contentScale: ContentScale = ContentScale.Fit,
    isRotation: Boolean = false,
    isZoomable: Boolean = true,
    lazyState: LazyListState
) {
    val scale = remember { mutableStateOf(1f) }
    val rotationState = remember { mutableStateOf(1f) }
    val offsetX = remember { mutableStateOf(1f) }
    val offsetY = remember { mutableStateOf(1f) }

    val coroutineScope = rememberCoroutineScope()
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .background(Color.Transparent)
            .combinedClickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null,
                onClick = { /* NADA :) */ },
                onDoubleClick = {
                    if (scale.value >= 2f) {
                        scale.value = 1f
                        offsetX.value = 1f
                        offsetY.value = 1f
                    } else scale.value = 3f
                },
            )
            .pointerInput(Unit) {
                if (isZoomable) {
                    forEachGesture {
                        awaitPointerEventScope {
                            awaitFirstDown()
                            do {
                                val event = awaitPointerEvent()
                                scale.value *= event.calculateZoom()
                                if (scale.value > 1) {
                                    coroutineScope.launch {
                                        lazyState.setScrolling(false)
                                    }
                                    val offset = event.calculatePan()
                                    offsetX.value += offset.x
                                    offsetY.value += offset.y
                                    rotationState.value += event.calculateRotation()
                                    coroutineScope.launch {
                                        lazyState.setScrolling(true)
                                    }
                                } else {
                                    scale.value = 1f
                                    offsetX.value = 1f
                                    offsetY.value = 1f
                                }
                            } while (event.changes.any { it.pressed })
                        }
                    }
                }
            }

    ) {
        Image(
            bitmap = bitmap,
            contentDescription = null,
            contentScale = contentScale,
            modifier = modifier
                .align(Alignment.Center)
                .graphicsLayer {
                    if (isZoomable) {
                        scaleX = maxOf(maxScale, minOf(minScale, scale.value))
                        scaleY = maxOf(maxScale, minOf(minScale, scale.value))
                        if (isRotation) {
                            rotationZ = rotationState.value
                        }
                        translationX = offsetX.value
                        translationY = offsetY.value
                    }
                }
        )
    }
}

它是可缩放的,可旋转的(如果你想要它),如果图像放大则支持平移,支持 double-click zoom-in 和 zoom-out 并且还支持被使用在可滚动元素内。我还没有想出一个解决方案来限制用户可以平移图像的距离。

它使用 combinedClickable,因此 double-click 缩放不会干扰其他手势,pointerInput 用于缩放、平移和旋转。

它使用此扩展函数来控制 LazyListState,但如果您需要它用于 ScrollState,修改它以满足您的需要应该不难:

suspend fun LazyListState.setScrolling(value: Boolean) {
    scroll(scrollPriority = MutatePriority.PreventUserInput) {
        when (value) {
            true -> Unit
            else -> awaitCancellation()
        }
    }
}

您可以根据需要随意修改它。