我想要一个包含行列的框,其中填充了更多子项,这些子项接受单击(“CL”)和长按(“LO”)以进行缩放和拖动。使用
pointerInput
和 detectTransforgestures
我可以根据需要改变子布局。
var zoom by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val outer = (1..60).toList().chunked(6)
Box(Modifier
.fillMaxSize()
.pointerInput(Unit) {
//zoom in/out and move around
detectTransformGestures { gestureCentroid, gesturePan, gestureZoom, _ ->
val oldScale = zoom
val newScale = (zoom * gestureZoom).coerceIn(0.5f..5f)
offset =
(offset + gestureCentroid / oldScale) - (gestureCentroid / newScale + gesturePan / oldScale)
zoom = newScale
}
}) {
Box(
Modifier
.graphicsLayer {
translationX = -offset.x * zoom
translationY = -offset.y * zoom
scaleX = zoom
scaleY = zoom
}
.background(Color.Cyan)
) {
Column {
outer.forEach { inner ->
Row {
inner.forEach { tile ->
var text by remember {
mutableStateOf(tile.toString())
}
Text(text,
Modifier
.padding(8.dp)
.combinedClickable(
onClick = {
text = "CL"
},
onLongClick = {
text = "LO"
}
)
.background(Color.Green)
.padding(8.dp)
)
}
}
}
}
}
}
现在的问题是,可点击的子项(标记为绿色)似乎吞下了点击手势,因此,当尝试捏两根手指时,如果我的手指点击按钮(如波纹所示),我将无法缩小回来蓝色或白色区域。
有什么办法不让可点击的子项消耗这种类型的事件或者拦截它,这样他们甚至不会收到像捏合这样的多点触控事件?
在jetpack Compose中,默认的PointerEventPass是Main,正如您所看到的在此答案中,手势从后代传播到祖先,而您希望变换手势从祖先传播到后代。
为此,您需要使用 PointerEventPass.Initial。当您触摸按钮时,使用 Final 将不起作用,因为它们会消耗事件。现在,青色背景将允许在按钮使用它们之前进行捏合手势,您也可以在指针数量大于 1 时使用事件,以不将单击或长按设置为
// 🔥Consume touch when multiple fingers down
// This prevents click and long click if your finger touches a
// button while pinch gesture is being invoked
val size = changes.size
if (size>1){
changes.forEach { it.consume() }
}
结果
您应该用于转换的代码是
suspend fun PointerInputScope.detectTransformGestures(
panZoomLock: Boolean = false,
consume: Boolean = true,
pass: PointerEventPass = PointerEventPass.Main,
onGestureStart: (PointerInputChange) -> Unit = {},
onGesture: (
centroid: Offset,
pan: Offset,
zoom: Float,
rotation: Float,
mainPointer: PointerInputChange,
changes: List<PointerInputChange>
) -> Unit,
onGestureEnd: (PointerInputChange) -> Unit = {}
) {
awaitEachGesture {
var rotation = 0f
var zoom = 1f
var pan = Offset.Zero
var pastTouchSlop = false
val touchSlop = viewConfiguration.touchSlop
var lockedToPanZoom = false
// Wait for at least one pointer to press down, and set first contact position
val down: PointerInputChange = awaitFirstDown(
requireUnconsumed = false,
pass = pass
)
onGestureStart(down)
var pointer = down
// Main pointer is the one that is down initially
var pointerId = down.id
do {
val event = awaitPointerEvent(pass = pass)
// If any position change is consumed from another PointerInputChange
// or pointer count requirement is not fulfilled
val canceled =
event.changes.any { it.isConsumed }
if (!canceled) {
// Get pointer that is down, if first pointer is up
// get another and use it if other pointers are also down
// event.changes.first() doesn't return same order
val pointerInputChange =
event.changes.firstOrNull { it.id == pointerId }
?: event.changes.first()
// Next time will check same pointer with this id
pointerId = pointerInputChange.id
pointer = pointerInputChange
val zoomChange = event.calculateZoom()
val rotationChange = event.calculateRotation()
val panChange = event.calculatePan()
if (!pastTouchSlop) {
zoom *= zoomChange
rotation += rotationChange
pan += panChange
val centroidSize = event.calculateCentroidSize(useCurrent = false)
val zoomMotion = abs(1 - zoom) * centroidSize
val rotationMotion =
abs(rotation * kotlin.math.PI.toFloat() * centroidSize / 180f)
val panMotion = pan.getDistance()
if (zoomMotion > touchSlop ||
rotationMotion > touchSlop ||
panMotion > touchSlop
) {
pastTouchSlop = true
lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
}
}
if (pastTouchSlop) {
val centroid = event.calculateCentroid(useCurrent = false)
val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
if (effectiveRotation != 0f ||
zoomChange != 1f ||
panChange != Offset.Zero
) {
onGesture(
centroid,
panChange,
zoomChange,
effectiveRotation,
pointer,
event.changes
)
}
if (consume) {
event.changes.forEach {
if (it.positionChanged()) {
it.consume()
}
}
}
}
}
} while (!canceled && event.changes.any { it.pressed })
onGestureEnd(pointer)
}
}
使用方法
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
private fun TouchComposable() {
var zoom by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val outer = (1..60).toList().chunked(6)
Box(
Modifier
.fillMaxSize()
.pointerInput(Unit) {
//zoom in/out and move around
detectTransformGestures(
pass = PointerEventPass.Initial,
onGesture = { gestureCentroid: Offset,
gesturePan: Offset,
gestureZoom: Float,
_,
_,
changes: List<PointerInputChange> ->
val oldScale = zoom
val newScale = (zoom * gestureZoom).coerceIn(0.5f..5f)
offset =
(offset + gestureCentroid / oldScale) - (gestureCentroid / newScale + gesturePan / oldScale)
zoom = newScale
// 🔥Consume touch when multiple fingers down
// This prevents click and long click if your finger touches a
// button while pinch gesture is being invoked
val size = changes.size
if (size > 1) {
changes.forEach { it.consume() }
}
}
)
}) {
Box(
Modifier
.graphicsLayer {
translationX = -offset.x * zoom
translationY = -offset.y * zoom
scaleX = zoom
scaleY = zoom
}
.background(Color.Cyan)
) {
Column {
outer.forEach { inner ->
Row {
inner.forEach { tile ->
var text by remember {
mutableStateOf(tile.toString())
}
Text(text,
Modifier
.padding(8.dp)
.combinedClickable(
onClick = {
text = "CL"
},
onLongClick = {
text = "LO"
}
)
.background(Color.Green)
.padding(8.dp)
)
}
}
}
}
}
}
}
您还可以在这个库中找到这个手势和其他一些手势
https://github.com/SmartToolFactory/Compose-Extended-Gestures
本教程中有更多关于手势的信息
https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials