kotlin 喷气背包合成中更好的缩放行为

7fyelxc5  于 2023-10-23  发布在  Kotlin
关注(0)|答案(3)|浏览(150)

默认的缩放行为,如在撰写文档中所解释的,会干扰拖动手势,并围绕可缩放对象的中心旋转和缩放,而不是手指
有没有更好的方法来做到这一点?

xghobddn

xghobddn1#

我把这个解决方案的代码变成了一个库:de.mr-pine.utils:zoomables

你必须使用pointerInputScope与detectTransformGestures和这个函数作为你的onGesture:

fun onTransformGesture(
    centroid: Offset,
    pan: Offset,
    zoom: Float,
    transformRotation: Float
) {
    offset += pan
    scale *= zoom
    rotation += transformRotation

    val x0 = centroid.x - imageCenter.x
    val y0 = centroid.y - imageCenter.y

    val hyp0 = sqrt(x0 * x0 + y0 * y0)
    val hyp1 = zoom * hyp0 * (if (x0 > 0) {
        1f
    } else {
        -1f
    })

    val alpha0 = atan(y0 / x0)

    val alpha1 = alpha0 + (transformRotation * ((2 * PI) / 360))

    val x1 = cos(alpha1) * hyp1
    val y1 = sin(alpha1) * hyp1

    transformOffset =
        centroid - (imageCenter - offset) - Offset(x1.toFloat(), y1.toFloat())
    offset = transformOffset
}

以下是如何围绕触摸输入旋转/缩放的示例,该示例还支持滑动和双击以重置缩放/放大:

val scope = rememberCoroutineScope()

var scale by remember { mutableStateOf(1f) }
var rotation by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
    scale *= zoomChange
    rotation += rotationChange
    offset += offsetChange
}

var dragOffset by remember { mutableStateOf(Offset.Zero) }
var imageCenter by remember { mutableStateOf(Offset.Zero) }
var transformOffset by remember { mutableStateOf(Offset.Zero) }

Box(
    Modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = {
                    if (scale != 1f) {
                        scope.launch {
                            state.animateZoomBy(1 / scale)
                        }
                        offset = Offset.Zero
                        rotation = 0f
                    } else {
                        scope.launch {
                            state.animateZoomBy(2f)
                        }
                    }
                }
            )
        }
        .pointerInput(Unit) {
            val panZoomLock = true
            forEachGesture {
                awaitPointerEventScope {
                    var transformRotation = 0f
                    var zoom = 1f
                    var pan = Offset.Zero
                    var pastTouchSlop = false
                    val touchSlop = viewConfiguration.touchSlop
                    var lockedToPanZoom = false
                    var drag: PointerInputChange?
                    var overSlop = Offset.Zero

                    val down = awaitFirstDown(requireUnconsumed = false)

                    var transformEventCounter = 0
                    do {
                        val event = awaitPointerEvent()
                        val canceled = event.changes.fastAny { it.positionChangeConsumed() }
                        var relevant = true
                        if (event.changes.size > 1) {
                            if (!canceled) {
                                val zoomChange = event.calculateZoom()
                                val rotationChange = event.calculateRotation()
                                val panChange = event.calculatePan()

                                if (!pastTouchSlop) {
                                    zoom *= zoomChange
                                    transformRotation += rotationChange
                                    pan += panChange

                                    val centroidSize =
                                        event.calculateCentroidSize(useCurrent = false)
                                    val zoomMotion = abs(1 - zoom) * centroidSize
                                    val rotationMotion =
                                        abs(transformRotation * PI.toFloat() * centroidSize / 180f)
                                    val panMotion = pan.getDistance()

                                    if (zoomMotion > touchSlop ||
                                        rotationMotion > touchSlop ||
                                        panMotion > touchSlop
                                    ) {
                                        pastTouchSlop = true
                                        lockedToPanZoom =
                                            panZoomLock && rotationMotion < touchSlop
                                    }
                                }

                                if (pastTouchSlop) {
                                    val eventCentroid = event.calculateCentroid(useCurrent = false)
                                    val effectiveRotation =
                                        if (lockedToPanZoom) 0f else rotationChange
                                    if (effectiveRotation != 0f ||
                                        zoomChange != 1f ||
                                        panChange != Offset.Zero
                                    ) {
                                        onTransformGesture(
                                            eventCentroid,
                                            panChange,
                                            zoomChange,
                                            effectiveRotation
                                        )
                                    }
                                    event.changes.fastForEach {
                                        if (it.positionChanged()) {
                                            it.consumeAllChanges()
                                        }
                                    }
                                }
                            }
                        } else if (transformEventCounter > 3) relevant = false
                        transformEventCounter++
                    } while (!canceled && event.changes.fastAny { it.pressed } && relevant)

                    do {
                        val event = awaitPointerEvent()
                        drag = awaitTouchSlopOrCancellation(down.id) { change, over ->
                            change.consumePositionChange()
                            overSlop = over
                        }
                    } while (drag != null && !drag.positionChangeConsumed())
                    if (drag != null) {
                        dragOffset = Offset.Zero
                        if (scale !in 0.92f..1.08f) {
                            offset += overSlop
                        } else {
                            dragOffset += overSlop
                        }
                        if (drag(drag.id) {
                                if (scale !in 0.92f..1.08f) {
                                    offset += it.positionChange()
                                } else {
                                    dragOffset += it.positionChange()
                                }
                                it.consumePositionChange()
                            }
                        ) {
                            if (scale in 0.92f..1.08f) {
                                val offsetX = dragOffset.x
                                if (offsetX > 300) {
                                    onSwipeRight()

                                } else if (offsetX < -300) {
                                    onSwipeLeft()
                                }
                            }
                        }
                    }
                }
            }
        }
) {
    ZoomComposable(
        modifier = Modifier
            .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
            .graphicsLayer(
                scaleX = scale - 0.02f,
                scaleY = scale - 0.02f,
                rotationZ = rotation
            )
            .onGloballyPositioned { coordinates ->
                val localOffset =
                    Offset(
                        coordinates.size.width.toFloat() / 2,
                        coordinates.size.height.toFloat() / 2
                    )
                val windowOffset = coordinates.localToWindow(localOffset)
                imageCenter = coordinates.parentLayoutCoordinates?.windowToLocal(windowOffset)
                    ?: Offset.Zero
            }
    )
}
gywdnpxw

gywdnpxw2#

这是一个非常简单的可缩放图像。

@Composable
fun ZoomableImage() {
    var scale by remember { mutableStateOf(1f) }
    var offset by remember { mutableStateOf(Offset.Zero) }

    Box(
        Modifier
            .size(600.dp)
    ) {
        Image(
            painter = rememberImagePainter(data = "https://picsum.photos/600/600"),
            contentDescription = "A Content description",
            modifier = Modifier
                .align(Alignment.Center)
                .graphicsLayer(
                    scaleX = scale,
                    scaleY = scale,
                    translationX = if (scale > 1f) offset.x else 0f,
                    translationY = if (scale > 1f) offset.y else 0f
                )
                .pointerInput(Unit) {
                    detectTransformGestures(
                        onGesture = { _, pan: Offset, zoom: Float, _ ->
                            offset += pan
                            scale = (scale * zoom).coerceIn(0.5f, 4f)
                        }
                    )
                }
        )
    }
}

仅支持缩放和平移。旋转和双击不是。为了获得稍微平滑的平移效果,可以对pan应用一个小乘数,如下所示:

offset += pan * 1.5f

我还添加了coerceIn,以避免放大/缩小,直到边界看起来很奇怪。如果需要,请随意删除coerceIn。您还可以删除包含BoxAlignment。平移(平移)将只适用于如果我们以前缩放。这看起来更自然IMHO。
欢迎反馈和改进

z31licg0

z31licg03#

var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
var rotation by remember { mutableFloatStateOf(0f) }    
fun Offset.rotateBy(angle: Double): Offset {
  return Offset((x*cos(angle) - y*sin(angle)).toFloat(), (x*sin(angle) + y*cos(angle)).toFloat())
}
val state = rememberTransformableState { zoomChange, offsetChange, rotChange ->
    offset = offset.rotateBy(rotChange*PI/180) + offsetChange/scale
    scale *= zoomChange; rotation += rotChange
    if (scale > 10f) scale = 10f; if (scale < .1f) scale = .1f
}
Box(modifier = Modifier.fillMaxSize().transformable(state)) {
    AsyncImage(model = yourModel, contentDescription = null,
        contentScale = ContentScale.None,
        modifier = Modifier.wrapContentSize(unbounded = true)
        .graphicsLayer(scaleX = scale, scaleY = scale, rotationZ = rotation,
        translationX = scale*offset.x, translationY = scale*offset.y)
    )
}

相关问题