我在一个项目中遇到了这个有趣的问题,用户应该能够在定义的形状上绘制相同的形状,到目前为止我已经实现了这一点,但我想检查他/她是否在 ONE 中正确绘制了形状去。如果手指超出广场,当前绘图应重置并放置一条 toast 消息,表示未成功,否则显示成功,我如何检查绘图是否在广场上?
白色方 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)
}
结果
如果您的形状很复杂,您可以做的是获取路径段并检查它们是否超出复杂形状的范围
val segments: Iterable<PathSegment> = path.asAndroidPath().flatten()
pathSegment 具有起始和结束 PointF
值。如果用户快速移动指针,它可能无法创建足够的路径段,但这可能是一种边缘情况。
本教程有关于路径段检查的部分,上面的示例将给出一个想法。但这对于复杂的形状可能会非常困难,这可能需要您询问用于检测位置是否在路径内的算法问题
https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials#6-1-5-canvas-path-segments
我看到您使用我的代码在我提到的 Canvas 上绘图 here 。我简化了你可以查看these gestures看看现在有多简单。您不需要所有这些代码。
关于android - Jetpack compose 在形状上绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72968632/