android - Jetpack compose 在形状上绘图

标签 android algorithm kotlin drawing android-jetpack-compose

我在一个项目中遇到了这个有趣的问题,用户应该能够在定义的形状上绘制相同的形状,到目前为止我已经实现了这一点,但我想检查他/她是否在 ONE 中正确绘制了形状去。如果手指超出广场,当前绘图应重置并放置一条 toast 消息,表示未成功,否则显示成功,我如何检查绘图是否在广场上?

image

白色方 block 是用drawRect()方法绘制的,并由用户自己在其上绘制,通过Drawpath()实现。 代码如下

class DrawingActivity : ComponentActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyDrawing()
        }
    }
}
@Composable
fun MyDrawing() {
    val actionIdle = 0
    val actionDown = 1
    val actionMove = 2
    val actionUp = 3
    //Path, current touch position and touch states
    val path = remember { Path() }
    var motionEvent by remember { mutableStateOf(actionIdle) }
    var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
    val canvasColor: Color by remember { mutableStateOf(Color.LightGray) }
    val drawModifier = Modifier
        .fillMaxWidth()
        .fillMaxHeight()
        .background(canvasColor)
        .clipToBounds()
        .pointerInput(Unit) {
            forEachGesture {
                awaitPointerEventScope {

                    val down: PointerInputChange = awaitFirstDown().also {
                        motionEvent = actionDown
                        currentPosition = it.position
                    }
                    do {
                        val event: PointerEvent = awaitPointerEvent()

                        var eventChanges =
                            "DOWN changedToDown: ${down.changedToDown()} changedUp: ${down.changedToUp()}\n"
                        event.changes
                            .forEachIndexed { index: Int, pointerInputChange: PointerInputChange ->
                                eventChanges += "Index: $index, id: ${pointerInputChange.id}, " +
                                        "changedUp: ${pointerInputChange.changedToUp()}" +
                                        "pos: ${pointerInputChange.position}\n"

                                pointerInputChange.consumePositionChange()
                            }

                        //gestureText = "EVENT changes size ${event.changes.size}\n" + eventChanges

                        //gestureColor = Color.Green
                        motionEvent = actionMove
                        currentPosition = event.changes.first().position
                    } while (event.changes.any { it.pressed })

                    motionEvent = actionUp
                    //canvasColor = Color.LightGray

                    //gestureText += "UP changedToDown: ${down.changedToDown()} " + "changedUp: ${down.changedToUp()}\n"
                }
            }

        }


    Canvas(
        modifier = drawModifier
            .padding(20.dp)
            .size(500.dp)
    ) {
        val canvasWidth = size.width
        val canvasHeight = size.height
        val line = 1.5
        val squareSize = canvasWidth/line

        drawRect(
            color = Color.White,
            topLeft = Offset(center.x - canvasWidth / 3, center.y - canvasHeight / 6),
            size = Size(width = squareSize.toFloat(), squareSize.toFloat()),
            style = Stroke(
                width = 50.dp.toPx()
            ),
        )

        when(motionEvent){
            actionDown->{
                path.moveTo(currentPosition.x,currentPosition.y)
            }
            actionMove->{
                if (currentPosition!= Offset.Unspecified){
                    path.lineTo(currentPosition.x,currentPosition.y)

                }
            }
            actionUp->{
                path.lineTo(currentPosition.x,currentPosition.y)
                motionEvent = actionIdle


            }
            else-> Unit
        }

       drawPath(
           color = Color.Cyan,
           path = path,
           style = Stroke(width = 5.dp.toPx(), join = StrokeJoin.Round)
       )

    }

}

最佳答案

您可以使用path.getBounds()获取路径的矩形,并将其与用户当前的触摸位置进行比较。 在这里我为此添加了一个示例。我不检查它是否有错误或一键完成,您可以实现它。这个检查我们当前所处的边界,如果我们在绿色矩形中,我们就处于正确的边界

@Composable
private fun CanvasShapeSample() {

    // This is motion state. Initially or when touch is completed state is at MotionEvent.Idle
    // When touch is initiated state changes to MotionEvent.Down, when pointer is moved MotionEvent.Move,
    // after removing pointer we go to MotionEvent.Up to conclude drawing and then to MotionEvent.Idle
    // to not have undesired behavior when this composable recomposes. Leaving state at MotionEvent.Up
    // causes incorrect drawing.
    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) }

    val innerPath = remember { Path() }
    val outerPath = remember { Path() }

    // Path is what is used for drawing line on Canvas
    val path = remember { Path() }


    var isError by remember { mutableStateOf(false) }


    val drawModifier = Modifier
        .fillMaxSize()
        .background(Color.LightGray)
        .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
        )

    Canvas(modifier = drawModifier) {

        val canvasWidth = size.width
        val canvasHeight = size.height

        val outerShapeWidth = canvasWidth * .8f
        val innerShapeWidth = canvasWidth * .6f

        if (innerPath.isEmpty) {

            innerPath.addRect(
                Rect(
                    offset = Offset(
                        (canvasWidth - innerShapeWidth) / 2,
                        (canvasHeight - innerShapeWidth) / 2
                    ),
                    size = Size(innerShapeWidth, innerShapeWidth)
                )
            )
        }


        if (outerPath.isEmpty) {
            outerPath.addRect(
                Rect(
                    offset = Offset(
                        (canvasWidth - outerShapeWidth) / 2,
                        (canvasHeight - outerShapeWidth) / 2
                    ),
                    size = Size(outerShapeWidth, outerShapeWidth)
                )
            )
        }


        when (motionEvent) {
            MotionEvent.Down -> {
                path.moveTo(currentPosition.x, currentPosition.y)
                previousPosition = currentPosition
                isError = !isInBound(innerPath = innerPath, outerPath = outerPath, currentPosition)
            }

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

                )
                previousPosition = currentPosition
                isError = !isInBound(innerPath = innerPath, outerPath = outerPath, currentPosition)
            }

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

            else -> Unit
        }

        drawPath(color = Color.Green, path = outerPath)
        drawPath(color = Color.Yellow, path = innerPath)


        drawPath(
            color = Color.Red,
            path = path,
            style = Stroke(width = 4.dp.toPx(), cap = StrokeCap.Round, join = StrokeJoin.Round)
        )

        drawCircle(
            color = if (isError) Color.Red else Color.Green,
            center = Offset(100f, 100f),
            radius = 50f
        )
    }
}

private fun isInBound(innerPath: Path, outerPath: Path, position: Offset): Boolean {
    val innerRect = innerPath.getBounds()
    val outerRect = outerPath.getBounds()

    return !innerRect.contains(position) && outerRect.contains(position)
}

结果

enter image description here

如果您的形状很复杂,您可以做的是获取路径段并检查它们是否超出复杂形状的范围

        val segments: Iterable<PathSegment> = path.asAndroidPath().flatten()

pathSegment 具有起始和结束 PointF 值。如果用户快速移动指针,它可能无法创建足够的路径段,但这可能是一种边缘情况。

本教程有关于路径段检查的部分,上面的示例将给出一个想法。但这对于复杂的形状可能会非常困难,这可能需要您询问用于检测位置是否在路径内的算法问题

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials#6-1-5-canvas-path-segments

enter image description here

我看到您使用我的代码在我提到的 Canvas 上绘图 here 。我简化了你可以查看these gestures看看现在有多简单。您不需要所有这些代码。

关于android - Jetpack compose 在形状上绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72968632/

相关文章:

android - MVVMCross PictureChooser 插件在 Android 上(不再)工作

android - 将函数调用转换为 Lambda (SAM)

android - 我在使用 flutter 时遇到了 generatedPluginRegistrant.registerWith 方法和 MethodChannel 对象的问题

android - 如何在 onPostExecute() 中从 doInBackground() 添加数据到 BaseAdapter?

php - Android php mysql 的 HTTP post 方法

度数和距离定位系统的算法

c - 生成单词列表时数据输出错误

带有默认值的 Kotlin 数据类上的 Spring 构造函数注释

java - ViewModelProvider Fragment实例化模型

c++ - 两个整数的幂