我在一个项目中遇到了一个有趣的问题,在这个项目中,用户应该能够在定义的形状上绘制相同的形状,到目前为止,我已经实现了这一点,但我想判断他/她是否一次性正确绘制了形状.如果手指超出sqaure,则当前绘图应重置,并将toast消息设置为Unscssesfuld,否则表示Successfull,如何判断绘图是否在正方形上?

image

白色正方形是用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()获得路径的矩形,并将其与用户当前的touch 位置进行比较.

@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个开始和结束值.如果用户快速移动指针,可能不会创建足够的路径段,但可能是边缘情况.

本教程有关于路径段判断的部分,上面的示例将给出一个 idea .但对于复杂形状来说,这可能非常困难,因为如果位置在路径内,可能需要您询问检测算法

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

enter image description here

我看到你们用我的代码在画布上画画,我提到了here.我简化了它,你可以判断these gestures,看看它现在有多简单.你不需要所有的代码.

Android相关问答推荐

如何使用Jetpack Compose使水平pager 显示离屏页面?

处理Room数据库中的嵌套实体

如何将Unicode符号作为文本居中放置在布局中的按钮(Android XML)中?

Android布局渲染问题

FireBase Android ChildEventListener在被规则拒绝时触发(RTDB)

Visual Studio 2022,毛伊岛,Android Emulator:无法更改内存量

使用 settings.gradle 文件将 Firebase 依赖项添加到 Android 项目

如何从包装在泛型中的 retrofit 调用中检索密钥?

如何在没有人窃取令牌的情况下使用我的移动应用程序中的 API

在 react native 中设置 react-native-paper 组件的样式

如何将可重用的 ExtendedFloatingActionButton 与可重用的脚手架链接起来

获取 ArithmeticException:除以零,但我没有在任何地方除以零

每次在 Jetpack Compose 中调用导航

如何像 XML 一样在 Compose Android Studio 中折叠/展开小部件代码区域/区域

记住或不记得derivedStateOf

如何在 compose 中使用 BottomSheetScaffold 为底页设置半展开高度?

将应用更改为暗模式后 Android MainActivity 数据泄漏

如何为具有不同屏幕尺寸但相同密度的手机创建响应式布局?

Jetpack Compose:对角拆分卡片并将内容放入其中

不能在kotlin的lazycolumn中使用列表