在compose文档中解释的默认缩放行为会干扰dragGestures,并围绕可缩放的中心旋转和缩放,而不是您的手指
有更好的方法吗?
我将这个解决方案中的代码放入了一个库:de.先生-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
}
)
}
这是一个非常简单的可缩放图像。
@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
。您也可以去除含有Box
和Alignment
。平移(平移)只会在我们之前缩放过的情况下应用。这样看起来更自然。
欢迎反馈和改进