简体   繁体   English

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

[英]How to use detectTransformGestures but not consuming all pointer event

I was making a fullscreen photo viewer which contain a pager (used HorizontalPager ) and each page, user can zoom in/out and pan the image, but still able to swipe through pages.我正在制作一个全屏照片查看器,其中包含一个寻呼机(使用HorizontalPager )和每个页面,用户可以放大/缩小和平移图像,但仍然能够浏览页面。

My idea is swiping page will occurs when the image is not zoomed in (scale factor = 1), if it's zoomed in (scale factor > 1) then dragging/swiping will pan the image around.我的想法是,当图像未放大(比例因子 = 1)时,将发生滑动页面,如果它被放大(比例因子 > 1),则拖动/滑动将平移图像。

Here is the code for the HorizontalPager that contain my customized zoomable Image:这是包含我自定义的可缩放图像的HorizontalPager ntalPager 的代码:

@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
                    }
                }
            }
    )
}
}

So my image is zoomed in maximum 3f, and cannot zoom out smaller than 0.所以我的图像最大放大 3f,并且不能缩小小于 0。

I cannot swipe to change to another page if detectTransformGestures is in my code.如果detectTransformGestures在我的代码中,我无法滑动以更改到另一个页面。 If I put the detectTransformGestures based on the factor (scale = 1, make it swipeable to another page if not zoomed in), then it will be a "deadlock" as I cannot zoom in because there is no listener.如果我根据因素放置detectTransformGestures (scale = 1,如果不放大,使其可滑动到另一个页面),那么它将是一个“死锁”,因为我无法放大,因为没有听众。

I don't know if there is some how to make it possible...我不知道是否有一些方法可以使它成为可能...

Thank you guys for your time!谢谢大家的宝贵时间!

I had to do something similar, and came up with 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
                    }
                }
        )
    }
}

It is zoomable, rotatable (if you want it), supports pan if the image is zoomed in, has support for double-click zoom-in and zoom-out and also supports being used inside a scrollable element.它是可缩放的,可旋转的(如果你想要它),如果图像被放大则支持平移,支持双击放大和缩小,还支持在可滚动元素内使用。 I haven't come up with a solution to limit how far can the user pan the image yet.我还没有想出一个解决方案来限制用户平移图像的距离。

It uses combinedClickable so the double-click zoom works without interfering with the other gestures, and pointerInput for the zoom, pan and rotation.它使用combinedClickable ,因此双击缩放不会干扰其他手势,并pointerInput进行缩放、平移和旋转。

It uses this extension function to control the LazyListState , but if you need it for ScrollState it shouldn't be hard to modify it to suit your needs:它使用此扩展名 function 来控制LazyListState ,但是如果您需要它用于ScrollState ,那么修改它以满足您的需要应该不难:

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

Feel free to modify it for your needs.随意修改它以满足您的需求。

Solution for HorizontalPager with better ux on swipe:具有更好的滑动用户体验的 HorizontalPager 解决方案:

val pagerState = rememberPagerState()
val scrollEnabled = remember { mutableStateOf(true) }
HorizontalPager(
   count = ,
   state = pagerState,
   userScrollEnabled = scrollEnabled.value,
) { }


@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ZoomablePagerImage(
    modifier: Modifier = Modifier,
    painter: Painter,
    scrollEnabled: MutableState<Boolean>,
    minScale: Float = 1f,
    maxScale: Float = 5f,
    contentScale: ContentScale = ContentScale.Fit,
    isRotation: Boolean = false,
) {
    var targetScale by remember { mutableStateOf(1f) }
    val scale = animateFloatAsState(targetValue = maxOf(minScale, minOf(maxScale, targetScale)))
    var rotationState by remember { mutableStateOf(1f) }
    var offsetX by remember { mutableStateOf(1f) }
    var offsetY by remember { mutableStateOf(1f) }
    val configuration = LocalConfiguration.current
    val screenWidthPx = with(LocalDensity.current) { configuration.screenWidthDp.dp.toPx() }
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .background(Color.Transparent)
            .combinedClickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null,
                onClick = { },
                onDoubleClick = {
                    if (targetScale >= 2f) {
                        targetScale = 1f
                        offsetX = 1f
                        offsetY = 1f
                        scrollEnabled.value = true
                    } else targetScale = 3f
                },
            )
            .pointerInput(Unit) {
                forEachGesture {
                    awaitPointerEventScope {
                        awaitFirstDown()
                        do {
                            val event = awaitPointerEvent()
                            val zoom = event.calculateZoom()
                            targetScale *= zoom
                            val offset = event.calculatePan()
                            if (targetScale <= 1) {
                                offsetX = 1f
                                offsetY = 1f
                                targetScale = 1f
                                scrollEnabled.value = true
                            } else {
                                offsetX += offset.x
                                offsetY += offset.y
                                if (zoom > 1) {
                                    scrollEnabled.value = false
                                    rotationState += event.calculateRotation()
                                }
                                val imageWidth = screenWidthPx * scale.value
                                val borderReached = imageWidth - screenWidthPx - 2 * abs(offsetX)
                                scrollEnabled.value = borderReached <= 0
                                if (borderReached < 0) {
                                    offsetX = ((imageWidth - screenWidthPx) / 2f).withSign(offsetX)
                                    if (offset.x != 0f) offsetY -= offset.y
                                }
                            }
                        } while (event.changes.any { it.pressed })
                    }
                }
            }

    ) {
        Image(
            painter = painter,
            contentDescription = null,
            contentScale = contentScale,
            modifier = modifier
                .align(Alignment.Center)
                .graphicsLayer {
                    this.scaleX = scale.value
                    this.scaleY = scale.value
                    if (isRotation) {
                        rotationZ = rotationState
                    }
                    this.translationX = offsetX
                    this.translationY = offsetY
                }
        )
    }
}

If you can create a mutable state variable that keeps track of the zoom factor, you can add the pointerInput modifier when the zoom factor is greater than one and leave it out when it is greater than one.如果您可以创建一个可变的 state 变量来跟踪缩放因子,则可以在缩放因子大于 1 时添加 pointerInput 修饰符,并在大于 1 时将其保留。 Something like this:像这样:

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
            }
        }

)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Android 撰写detectTransformGestures 动作结束 - Android Compose detectTransformGestures action end 在 Compose 中,如何在不消耗所有可用高度的情况下设置最小高度(minHeight)? - In Compose, how to set minimum height (minHeight) without consuming all available height? Kotlin Jetpack Compose开发中如何使用协程在后台执行耗时操作和更新UI Android - How to perform time-consuming operations in the background and update the UI using coroutines in Kotlin for Android development in Jetpack Compose 如何在 onClick 事件中调用可组合函数 - How to call a composable function in onClick event Jetpack Compose 中的单个事件如何工作? - How single event works in Jetpack Compose? 如何在撰写中使用 mutableStateListOf - How to use mutableStateListOf in compose 如何在android compose ui中禁用开关对触摸事件的响应? - How to disable switch's response to touch event in android compose ui? Android Compose - 如何处理 JetPackCompose 中的 ViewModel 清除焦点事件? - Android Compose - How to handle ViewModel clear focus event in JetPackCompose? 如何在主 Activity 中发生事件后重组可组合项? - How to recompose a composable after an event occured in the main Activity? 如何启动一个永远不应该从 Compose 事件处理程序中取消的协程 - How to launch a coroutine that should never be canceled from a Compose event handler
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM