默认的缩放行为,如在撰写文档中所解释的,会干扰拖动手势,并围绕可缩放对象的中心旋转和缩放,而不是手指有没有更好的方法来做到这一点?
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 } ) }
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应用一个小乘数,如下所示:
pan
offset += pan * 1.5f
我还添加了coerceIn,以避免放大/缩小,直到边界看起来很奇怪。如果需要,请随意删除coerceIn。您还可以删除包含Box和Alignment。平移(平移)将只适用于如果我们以前缩放。这看起来更自然IMHO。欢迎反馈和改进
coerceIn
Box
Alignment
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) ) }
3条答案
按热度按时间xghobddn1#
我把这个解决方案的代码变成了一个库:de.mr-pine.utils:zoomables
你必须使用pointerInputScope与detectTransformGestures和这个函数作为你的onGesture:
以下是如何围绕触摸输入旋转/缩放的示例,该示例还支持滑动和双击以重置缩放/放大:
gywdnpxw2#
这是一个非常简单的可缩放图像。
仅支持缩放和平移。旋转和双击不是。为了获得稍微平滑的平移效果,可以对
pan
应用一个小乘数,如下所示:我还添加了
coerceIn
,以避免放大/缩小,直到边界看起来很奇怪。如果需要,请随意删除coerceIn
。您还可以删除包含Box
和Alignment
。平移(平移)将只适用于如果我们以前缩放。这看起来更自然IMHO。欢迎反馈和改进
z31licg03#