pointerInteropFilter 未被描述为如果您不使用与现有视图代码互操作的触摸 api 的首选方式。
一个特殊的 PointerInputModifier 提供对底层的访问
MotionEvents 最初分派给 Compose。首选指针输入和
仅将其用于与消耗的现有代码的互操作
运动事件。虽然此修饰符的主要目的是允许
任意代码来访问派发到的原始 MotionEvent
Compose,为了完整起见,提供了类似物以允许任意
与系统交互的代码,就像它是一个 Android 视图一样。
您可以使用pointerInput ,awaitTouchDown 用于MotionEvent.ACTION_DOWN,awaitPointerEvent 用于MotionEvent.ACTION_MOVE 和MotionEvent.ACTION_UP
val pointerModifier = Modifier
.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown()
// ACTION_DOWN here
do {
//This PointerEvent contains details including
// event, id, position and more
val event: PointerEvent = awaitPointerEvent()
// ACTION_MOVE loop
// Consuming event prevents other gestures or scroll to intercept
event.changes.forEach { pointerInputChange: PointerInputChange ->
pointerInputChange.consumePositionChange()
}
} while (event.changes.any { it.pressed })
// ACTION_UP is here
}
}
}
关于手势的一些要点
- pointerInput 传播是指当您有多个来源时
从底部到顶部。
- 如果孩子和父母正在监听输入变化,它会传播
从内在的孩子到外在的父母。与来自的触摸事件不同
父母对孩子
- 如果您不使用事件,其他事件(如滚动拖动)可以
干扰或消耗事件,大多数事件检查它是否
在传播给他们之前消耗
detectDragGestures 源代码示例
val down = awaitFirstDown(requireUnconsumed = false)
var drag: PointerInputChange?
var overSlop = Offset.Zero
do {
drag = awaitPointerSlopOrCancellation(
down.id,
down.type
) { change, over ->
change.consumePositionChange()
overSlop = over
}
} while (drag != null && !drag.positionChangeConsumed())
-
所以当你需要阻止其他事件拦截时
在awaitFirstDown 之后调用pointerInputChange.consumeDown(),调用
awaitPointerEvent之后的pointerInputChange.consumePositionChange()
并且awaitFirstDown() 具有requireUnconsumed 参数,即
默认情况下为真。如果您将其设置为 false 即使指针输入消耗
在你的手势之前下来,你仍然得到它。这也是诸如拖动之类的事件如何使用它来首先向下移动。
-
您看到的每个可用事件detectDragGestures,
detectTapGestures 甚至 awaitFirstDown 使用 awaitPointerEvent
为了实现,所以使用awaitFirstDown,awaitPointerEvent
和消费变化您可以配置自己的手势。
例如,这是我从原始detectTransformGestures 定制的一个函数,只能在特定数量的指针向下调用时调用。
suspend fun PointerInputScope.detectMultiplePointerTransformGestures(
panZoomLock: Boolean = false,
numberOfPointersRequired: Int = 2,
onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit,
) {
forEachGesture {
awaitPointerEventScope {
var rotation = 0f
var zoom = 1f
var pan = Offset.Zero
var pastTouchSlop = false
val touchSlop = viewConfiguration.touchSlop
var lockedToPanZoom = false
awaitFirstDown(requireUnconsumed = false)
do {
val event = awaitPointerEvent()
val downPointerCount = event.changes.size
// If any position change is consumed from another pointer or pointer
// count that is pressed is not equal to pointerCount cancel this gesture
val canceled = event.changes.any { it.positionChangeConsumed() } || (
downPointerCount != numberOfPointersRequired)
if (!canceled) {
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 * 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)
}
event.changes.forEach {
if (it.positionChanged()) {
it.consumeAllChanges()
}
}
}
}
} while (!canceled && event.changes.any { it.pressed })
}
}
}
我还有一个tutorial here 详细介绍了手势