我正在开发 Jetpack Compose 应用程序,我想创建一个可以使用鼠标交互进行拖动和旋转的
Box
。我应该能够单击并拖动整个框以将其在屏幕上移动。我还想在盒子的顶部中心添加一个小手柄。当我拖动此手柄时,Box
应绕其中心旋转。
这是我迄今为止尝试过的:
@Composable
fun DragRotateBox() {
var rotation by remember { mutableStateOf(0f) }
var position by remember { mutableStateOf(Offset.Zero) }
var initialTouch = Offset.Zero
val boxSize = 100.dp
val handleSize = 20.dp
val boxSizePx = with(LocalDensity.current) { boxSize.toPx() }
val center = Offset(boxSizePx, boxSizePx)
// Main Box
Box(
modifier = Modifier
.graphicsLayer(
rotationZ = rotation,
translationX = position.x,
translationY = position.y
)
.background(Color.Blue)
.size(boxSize)
.pointerInput(Unit) {
detectDragGestures(
onDrag = {change, dragAmount ->
change.consume()
position += dragAmount
}
)
}
) {
// Rotation handler
Box(
modifier = Modifier
.size(handleSize)
.background(Color.Red)
.align(Alignment.TopCenter)
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset ->
initialTouch = offset
},
onDrag = { change, dragAmount ->
change.consume()
val angle = calculateRotationAngle(center, initialTouch, change.position)
rotation += angle
}
)
}
)
}
}
// Generated by ChatGPT!
fun calculateRotationAngle(pivot: Offset, initialTouch: Offset, currentTouch: Offset): Float {
val initialVector = initialTouch - pivot
val currentVector = currentTouch - pivot
val initialAngle = atan2(initialVector.y, initialVector.x)
val currentAngle = atan2(currentVector.y, currentVector.x)
return Math.toDegrees((currentAngle - initialAngle).toDouble()).toFloat()
}
单独实现时,拖动和旋转工作正常,但是当我尝试将拖动和旋转结合起来时,交互无法按预期工作。
这是该问题的演示:
我确信我错过了一些东西。谁能帮我解决这个问题吗?
当您应用
graphicsLayer
修改器旋转蓝色框时,所有以下修改器的坐标也会受到影响。当盒子旋转 180° 时,上变为下,左变为右:移动盒子现在已反转。
通常有两种方法可以解决这个问题:
pointerInput
类似的函数将蓝色框的 calculateRotationAngle
接收到的坐标翻译回来。pointerInput
混乱坐标之前应用 graphicsLayer
。
pointerInput
移动到修改器链的前面,则只有当您单击该框左上角的original 位置时才会检测到拖动手势。这是因为不仅旋转尚未应用(预期的目的),位置平移也尚未应用,因此从
pointerInput
的角度来看,盒子从未移动。仅在
graphicsLayer
中应用位置平移后。要解决此问题,您可以通过应用 one graphicsLayer
修饰符
before
pointerInput
(仅平移位置)和 another
graphicsLayer
修饰符 after
pointerInput
来引入单独的位置平移和旋转进行旋转。为了简化,您应该使用现有的修饰符 offset
和 rotation
而不是 graphicsLayer
。当您将蓝色框的修改器链更改为此时,一切都应该按预期工作:
modifier = Modifier
.offset { position.round() }
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, dragAmount ->
change.consume()
position += dragAmount
}
)
}
.rotate(rotation)
.background(Color.Blue)
.size(boxSize)