android 如何在Jetpack编写中打开TouchEvent?

2exbekwf  于 2023-02-02  发布在  Android
关注(0)|答案(4)|浏览(183)

在普通视图中,我们可以有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组合中,我只能发现修改器中有tapGestureFilter,它只接受ACTION_UP的操作。

Modifier
    .tapGestureFilter { Log.d("Track", "Tap ${it.x} | ${it.y}") }
    .doubleTapGestureFilter { Log.d("Track", "DoubleTap ${it.x} | ${it.y}") }

Jetpack合成是否有等效的onTouchEvent

ippsafx7

ippsafx71#

我们有一个单独的package,这是非常有用的。有两个主要的扩展函数适合你:

  • pointerInput-文档
  • pointerInteropFilter-文档

如果你想处理这个事件,我推荐使用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样本。
P.S.不要忘记@ExperimentalPointerInput注解。

kkih6yb8

kkih6yb82#

如果您不使用与现有View代码互操作的Touch API,则pointerInteropFilter不是首选使用方式。
一个特殊的PointerInputModifier,用于提供对最初调度到Compose的基础MotionEvent的访问。请首选pointerInput,并仅将其用于与使用MotionEvent的现有代码进行互操作。虽然此修饰符的主要目的是允许任意代码访问调度到Compose的原始MotionEvent,但为了完整起见,提供了类似物,使得任意代码能够像Android View一样与系统交互。
您可以将pointerInputawaitTouchDown用于MotionEvent.ACTION_DOWN,将awaitPointerEvent用于MotionEvent.ACTION_MOVEMotionEvent.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
            }
        }
}

关于手势的一些要点

  1. pointerInput传播是指当你有多个指针从底部一个指向顶部一个时。
    1.如果子节点和父节点正在侦听输入更改,则它会从内部子节点传播到外部子节点,然后再传播到父节点。
    1.如果您不使用事件,其他事件(如滚动拖动)可能会干扰或使用事件,大多数事件在传播给它们之前会检查它是否已使用
    示例的检测拖拽手势源代码
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(),在awaitPointerEvent之后调用指针输入更改.消费位置更改()
awaitFirstDown()requireUnconsumed参数,默认为true。如果你把它设置为false,即使pointerInput在你的手势之前消耗了down,你仍然会得到它。这也是像drag这样的事件如何使用它来获得第一个down的。
1.您看到的每个可用事件detectDragGesturesdetectTapGestures甚至awaitFirstDown都使用awaitPointerEvent来实现,因此使用awaitFirstDownawaitPointerEvent消耗更改,您可以配置自己的手势。
例如,这是一个从原始detectTransformGestures定制的函数i,它只在特定数目的指针向下时被调用。

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.consume()

是唯一用于防止其它手势/事件的一个。
我也有一个教程在这里,涵盖手势的细节

zujrkrfu

zujrkrfu3#

也许有点晚了,但由于compose是不断更新的,这就是我今天的做法:

Modifier
    .pointerInput(Unit) {
        detectTapGestures {...}
     }
    .pointerInput(Unit) {
        detectDragGestures { change, dragAmount ->  ...}
    })

我们还有detectHorizontalDragGesturesdetectVerticalDragGestures等帮助我们。
PS:1.0.0-beta03

kqhtkvqz

kqhtkvqz4#

在做了一些研究之后,看起来可以使用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确实有助于提供最后位置(如果需要)

相关问题