我有一个桌面应用程序,我想在其中实现缩放和平移行为,就像这个问题中一样,但没有滚动条。但是,我不知道如何允许用户在精确的点上进行滚轮缩放。我正在使用 Kotlin Compose Multiplatform 1.4.3
平移工作正常,但缩放后偏移计算不正确。缩放后鼠标光标应保持在相同位置。
这是我的代码:
fun main() = singleWindowApplication {
Surface {
Box {
PanAndZoom(
modifier = Modifier
.fillMaxSize()
) {
Box(modifier = Modifier.size(300.dp).background(Color.Gray))
}
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PanAndZoom(
modifier: Modifier = Modifier,
state: PanAndZoomState = remember { PanAndZoomState() },
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier = modifier
.onPointerEvent(PointerEventType.Scroll) {
val change = it.changes.first()
val delta = change.scrollDelta.y.toInt().sign
val position = change.position
val posBeforeZoom = state.screenToWorld(position)
// Zooming
state.scale(delta)
val posAfterZoom = state.screenToWorld(position)
// Incorrect offset calculation
state.offset += (posBeforeZoom - posAfterZoom)
}
.pointerInput(Unit) {
// Panning
detectDragGestures { _, dragAmount ->
state.offset += dragAmount
}
}
.onGloballyPositioned {
state.size = it.size
}
) {
Box(
modifier = Modifier
.matchParentSize()
.graphicsLayer {
with(state) {
// Applying transformations
scaleX = scale
scaleY = scale
translationX = offset.x
translationY = offset.y
}
},
contentAlignment = Alignment.Center,
content = content
)
}
}
class PanAndZoomState {
var size: IntSize = IntSize.Zero
var offset by mutableStateOf(Offset.Zero)
var scale by mutableStateOf(1f)
fun scale(delta: Int) {
scale = (scale * exp(delta * 0.2f)).coerceIn(0.25f, 1.75f)
}
fun screenToWorld(screenOffset: Offset): Offset {
return (screenOffset * scale) + ((size.toOffset() * (1 - scale) ) / 2f)
}
}
fun IntSize.toOffset(): Offset {
return Offset(
x = width.toFloat(),
y = height.toFloat()
)
}
不确定是否可以在 Kotlin Compose Multiplatform 1.4.3 中使用可转换,但可以在 1.5.10 中使用。我认为类似的东西可以工作:
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
}
fun scale(delta: Int) {
scale = (scale * exp(delta * 0.2f)).coerceIn(0.25f, 1.75f)
}
Box(
modifier = modifier
.graphicsLayer(
scaleX = scale,
scaleY = scale,
rotationZ = rotation,
translationX = offset.x,
translationY = offset.y
)
.transformable(state = state)
.fillMaxSize()
.focusable()
.onPointerEvent(PointerEventType.Scroll) {
val change = it.changes.first()
val delta = change.scrollDelta.y.toInt().sign
scale(delta)
} ...