我正在尝试制作一个可缩放和平移(在某些条件下)并且可以在 LazyList 中滚动的可组合项。我尝试利用pointerInteropFilter。我首先等待 slop 来了解应该处理 touch LazyList 或转换的内容。然而,pointerInteropFilter 似乎没有做任何事情。
val MAX_ZOOM = 3f
val MIN_ZOOM = 1f
Box(Modifier.padding(innerPadding)) {
LazyRow(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
items(10) {
var targetScale by remember { mutableStateOf(MIN_ZOOM) }
var targetOffset by remember { mutableStateOf(Offset.Zero) }
var slopReached by remember { mutableStateOf(false) }
val transformableState =
rememberTransformableState { zoomChange, offsetChange, _ ->
// until slope detected do nothing
if (!slopReached) return@rememberTransformableState
targetScale *= zoomChange
targetOffset = Offset(
targetOffset.x + offsetChange.x,
targetOffset.y + offsetChange.y
)
}
var slop by remember { mutableStateOf(Offset.Zero) }
Box(Modifier.pointerInteropFilter() {
// 2 fingers indicates it is zoom - must be handled by transformableState
if (it.pointerCount >= 2) return@pointerInteropFilter false
// zoomed image must be handled by transformableState
if (targetScale != MIN_ZOOM) return@pointerInteropFilter false
when (it.actionMasked) {
MotionEvent.ACTION_MOVE -> {
if (slopReached) {
// for unzoomed view
// horizontal gesture should be returned to LazyList
// vertical gesture should be handled by transformableState
return@pointerInteropFilter slop.x.absoluteValue > slop.y.absoluteValue
} else {
slop = Offset(
slop.x + it.x,
slop.y + it.y
)
slopReached =
slop.x.absoluteValue > 20 || slop.y.absoluteValue > 20
return@pointerInteropFilter false
}
}
}
true
}) {
Box(Modifier.transformable(transformableState)) {
Box(
Modifier
.background(Color.Red)
.graphicsLayer {
this.scaleX = targetScale
this.scaleY = targetScale
this.translationX = targetOffset.x
this.translationY = targetOffset.y
})
}
}
}
}
}
要在 LazyRow/Column 或任何可滚动内容中拥有可缩放/平移/旋转的可组合项,您需要有一个在特定条件下使用的转换手势。您可以编写一个像这个答案中那样的变换手势,并根据指针计数调用消费。
或者您可以编写一个不太复杂的触摸事件手势,例如
.pointerInput(Unit) {
awaitEachGesture {
// Wait for at least one pointer to press down
awaitFirstDown()
do {
val event = awaitPointerEvent()
// Calculate gestures and consume pointerInputChange
var zoom = item.zoom.value
zoom *= event.calculateZoom()
// Limit zoom between 100% and 300%
zoom = zoom.coerceIn(1f, 3f)
item.zoom.value = zoom
val pan = event.calculatePan()
val currentOffset = if (zoom == 1f) {
Offset.Zero
} else {
// This is for limiting pan inside Image bounds
val temp = item.offset.value + pan.times(zoom)
val maxX = (size.width * (zoom - 1) / 2f)
val maxY = (size.height * (zoom - 1) / 2f)
Offset(
temp.x.coerceIn(-maxX, maxX),
temp.y.coerceIn(-maxY, maxY)
)
}
item.offset.value = currentOffset
// When image is zoomed consume event and prevent scrolling
if (zoom > 1f) {
event.changes.forEach { pointerInputChange: PointerInputChange ->
pointerInputChange.consume()
}
}
} while (event.changes.any { it.pressed })
}
}
在此片段中没有 slopPass 检查,但您可以根据您的条件实现逻辑并调用pointerInputChang.consume()。可能是在缩放时,用户触摸了超过 1 个手指或按照您的逻辑倾斜。重要的是消耗可以防止其他滚动事件获得此手势。