繁体   English   中英

如何在 Jetpack Compose 中获取 onTouchEvent?

[英]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使用pointerInputawaitTouchDown ,对MotionEvent.ACTION_DOWNawaitPointerEvent使用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
            }
        }
}

在此处输入图像描述

关于手势的一些关键说明

  1. 指针输入传播是当你有多个从底部到顶部的时候。
  2. 如果孩子和父母正在监听输入变化,它会从内部孩子传播到外部然后是父母。 与从父母到孩子的触摸事件不同
  3. 如果您不使用事件,其他事件(例如滚动拖动)可能会干扰或消耗事件,大多数事件会在传播给它们之前检查它是否已被消耗

以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())
  1. 所以当你需要阻止其他事件拦截时

    在 awaitFirstDown 之后调用 pointerInputChange.consumeDown( pointerInputChange.consumeDown() ,在awaitFirstDown之后调用awaitPointerEvent ()

    并且awaitFirstDown()具有requireUnconsumed参数,默认情况下为 true。 如果您将其设置为 false 即使指针输入在您的手势之前消耗掉,您仍然可以得到它。 这也是诸如拖动之类的事件如何使用它来首先下降,无论如何。

  2. 您看到的每个可用事件detectDragGesturesdetectTapGestures甚至awaitFirstDown使用awaitPointerEvent来实现,因此使用awaitFirstDownawaitPointerEvent消费更改,您可以配置自己的手势。

例如,这是我从原始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 ->  ...}
    })

我们还有detectHorizontalDragGesturesdetectVerticalDragGestures等来帮助我们。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM