![](/img/trans.png)
[英]How can I get input connection from Jetpack Compose TextField?
[英]How can I get onTouchEvent in Jetpack Compose?
在普通视图中,我们可以有onTouchEvent
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {}
MotionEvent.ACTION_MOVE -> {}
MotionEvent.ACTION_UP -> {}
else -> return false
}
invalidate()
return true
}
在 Jetpack Compose 中,我只能发现我们在修改器中有tapGestureFilter
,它只接受来自ACTION_UP
的操作。
Modifier
.tapGestureFilter { Log.d("Track", "Tap ${it.x} | ${it.y}") }
.doubleTapGestureFilter { Log.d("Track", "DoubleTap ${it.x} | ${it.y}") }
Jetpack Compose 是否有等效的onTouchEvent
?
为此,我们有一个单独的包,非常有用。 有两个适合您的主要扩展功能:
如果您想处理和处理事件,我建议使用与pointerInteropFilter
类似的View.onTouchEvent
。 它与modifier
一起使用:
Column(modifier = Modifier.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {}
MotionEvent.ACTION_MOVE -> {}
MotionEvent.ACTION_UP -> {}
else -> false
}
true
})
这将是为您指定的View.onTouchEvent
示例编写调整后的代码。
PS 不要忘记@ExperimentalPointerInput
注释。
如果您不使用与现有视图代码互操作的触摸 api, pointerInteropFilter
描述为首选方式。
一个特殊的 PointerInputModifier,它提供对最初分派给 Compose 的底层 MotionEvent 的访问。 首选 pointerInput 并仅将其用于与使用 MotionEvents 的现有代码的互操作。 虽然此 Modifier 的主要目的是允许任意代码访问派发给 Compose 的原始 MotionEvent,但为了完整起见,提供了类似物以允许任意代码与系统交互,就好像它是一个 Android 视图一样。
您可以对MotionEvent.ACTION_UP
使用pointerInput
、 awaitTouchDown
,对MotionEvent.ACTION_DOWN
和awaitPointerEvent
使用MotionEvent.ACTION_MOVE
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
}
}
}
关于手势的一些关键说明
以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( pointerInputChange.consumeDown()
,在awaitFirstDown
之后调用awaitPointerEvent
()
并且awaitFirstDown()
具有requireUnconsumed
参数,默认情况下为 true。 如果您将其设置为 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 })
}
}
}
从1.2.0-beta01开始,不推荐使用像PointerInputChange.consemePositionChange()
、 PointerInputChange.consumeDownChange()
这样的部分消耗,以及消耗所有更改的部分消耗 PointerInputChange.consumeAllChanges( PointerInputChange.consumeAllChanges()
PointerInputChange.consume()
是唯一用于防止其他手势/事件的。
我这里还有一个教程,详细介绍了手势
也许有点晚了,但由于 compose 不断更新,这就是我今天的做法:
Modifier
.pointerInput(Unit) {
detectTapGestures {...}
}
.pointerInput(Unit) {
detectDragGestures { change, dragAmount -> ...}
})
我们还有detectHorizontalDragGestures
和detectVerticalDragGestures
等来帮助我们。
ps: 1.0.0-beta03
经过一番研究,看起来可以使用dragGestureFilter
,与tapGestureFilter
混合
Modifier
.dragGestureFilter(object: DragObserver {
override fun onDrag(dragDistance: Offset): Offset {
Log.d("Track", "onActionMove ${dragDistance.x} | ${dragDistance.y}")
return super.onDrag(dragDistance)
}
override fun onStart(downPosition: Offset) {
Log.d("Track", "onActionDown ${downPosition.x} | ${downPosition.y}")
super.onStart(downPosition)
}
override fun onStop(velocity: Offset) {
Log.d("Track", "onStop ${velocity.x} | ${velocity.y}")
super.onStop(velocity)
}
}, { true })
.tapGestureFilter {
Log.d("NGVL", "onActionUp ${it.x} | ${it.y}")
}
仍然使用tagGestureFilter
的原因是因为onStop
不提供位置,而只是速度,因此tapGestureFilter
确实有助于提供最后一个位置(如果需要)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.