如何使用触摸事件在 Jetpack Compose Canvas 上绘图?

Thr*_*ian 11 android android-jetpack-compose android-jetpack-compose-canvas

这是问答式的问题,因为我正在寻找使用 Jetpack Canvas 的绘图示例,但在 stackoverflow 上有问题,这个另一个,我发现可以使用pointerInteropFilter像 View 那样的绘图onTouchEvent MotionEvent,根据文档,这是不建议的

一个特殊的 PointerInputModifier,提供对最初调度到 Compose 的底层 MotionEvent 的访问。优先选择pointerInput ,并且仅将其用于与使用MotionEvents 的现有代码的互操作。

虽然此修饰符的主要目的是允许任意代码访问分派给 Compose 的原始 MotionEvent,但为了完整性,提供了类似物以允许任意代码与系统交互,就好像它是 Android 视图一样。

Thr*_*ian 18

编辑

自从我发布这个答案以来已经有一段时间了,当我从这个问题得到反馈时,之前的答案对初学者来说有点令人困惑,所以我简化了它,这个手势的库和更多内容可以在github repo中找到。

我们需要运动状态,就像 View 的第一个一样

enum class MotionEvent {
    Idle, Down, Move, Up
}
Run Code Online (Sandbox Code Playgroud)

需要空闲状态才能不将状态保留为 Up,因为如果发生任何重组,您的 Canvas 就会以 Up 状态重组,这会导致不需要的绘图甚至崩溃。

路径、当前触摸位置和触摸状态

var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }
// This is our motion event we get from touch motion
var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
// This is previous motion event before next touch is saved into this current position
var previousPosition by remember { mutableStateOf(Offset.Unspecified) }
Run Code Online (Sandbox Code Playgroud)

previousPosition是可选的,我使用它是因为我想 path.quadraticBezierTo在用指针移动时用 , 而不是 path.lineTo绘制平滑的线条

用于创建触摸事件的修饰符。Modifier.clipToBounds()是为了防止在 Canvas 之外绘图。

val drawModifier = Modifier
    .fillMaxWidth()
    .height(300.dp)
    .clipToBounds()
    .background(Color.White)
    .pointerMotionEvents(
        onDown = { pointerInputChange: PointerInputChange ->
            currentPosition = pointerInputChange.position
            motionEvent = MotionEvent.Down
            pointerInputChange.consume()
        },
        onMove = { pointerInputChange: PointerInputChange ->
            currentPosition = pointerInputChange.position
            motionEvent = MotionEvent.Move
            pointerInputChange.consume()
        },
        onUp = { pointerInputChange: PointerInputChange ->
            motionEvent = MotionEvent.Up
            pointerInputChange.consume()
        },
        delayAfterDownInMillis = 25L
    )
Run Code Online (Sandbox Code Playgroud)

Modifier.pointerMotionEvents我为它编写的自定义手势库是 onTouchEvent 的对应项,它可以在上面的 github 存储库中找到,这里有关于手势的详细说明,如果您不想,您可以轻松构建自己的手势。View 的 onTouchEvent 上发生第一次触摸后有延迟,在我的设备上约为 16 毫秒,这是我测量的最快的,我也添加到 Compose 上的手势,因为当用户最初有非常快速的指针移动时,Canvas 无法处理向下事件。

并将此修改器应用于画布并根据当前状态和位置移动或绘制

Canvas(modifier = drawModifier) {


    when (motionEvent) {
        MotionEvent.Down -> {
            path.moveTo(currentPosition.x, currentPosition.y)
            previousPosition = currentPosition
        }

        MotionEvent.Move -> {
            path.quadraticBezierTo(
                previousPosition.x,
                previousPosition.y,
                (previousPosition.x + currentPosition.x) / 2,
                (previousPosition.y + currentPosition.y) / 2

            )
            previousPosition = currentPosition
        }

        MotionEvent.Up -> {
            path.lineTo(currentPosition.x, currentPosition.y)
            currentPosition = Offset.Unspecified
            previousPosition = currentPosition
            motionEvent = MotionEvent.Idle
        }

        else -> Unit
    }

    drawPath(
        color = Color.Red,
        path = path,
        style = Stroke(width = 4.dp.toPx(), cap = StrokeCap.Round, join = StrokeJoin.Round)
    )
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

完整绘图应用程序的 Github 存储库也可以在这里找到。