Android Compose绘制路径来擦除前景

问题描述 投票:0回答:1

我通过在可组合项中摆弄画布来进行自我教育。

我正在寻找的想法是:

  1. 有背景颜色(在本例中只是做颜色渐变)
  2. 黑色前景覆盖背景
  3. 在屏幕上绘制一条路径,从某种意义上讲,它将擦除绘制它的黑色前景以暴露背景。

我已经成功地做到了这一点,但我对它如何以我偶然发现的解决方案的方式工作感到有点困惑。也许这毕竟是不对的,而且我的做法是错误的。

下面是我的代码:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //enableEdgeToEdge()
    setContent {
        SampleScratchPadTheme {

            var reset by remember {
                mutableStateOf(false)
            }

            Scaffold(modifier = Modifier.fillMaxSize(), floatingActionButton = {
                FloatingActionButton(onClick = {
                    reset = true
                }, shape = RoundedCornerShape(25.dp)) {
                    Icon(
                        imageVector = Icons.Default.Refresh,
                        contentDescription = "Restart"
                    )
                }
            }) { innerPadding ->

                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(innerPadding)
                ) {
                    Column {
                        Sample(reset, resetCanvas = {
                            reset = false
                        })
                    }

                }
            }
        }
    }
}

@Composable
private fun Sample(
    reset : Boolean,
    resetCanvas : () -> Unit
) {

    var circles = remember {
        mutableStateListOf(Offset(-100f, -100f)) // start off screen
    }

    var erasedPoints by remember {
        mutableStateOf(Path())
    }

    fun changeColor(): List<Color> {
        return listOf(
            Color(
                Random.nextInt(255),
                Random.nextInt(255),
                Random.nextInt(255),
                255
            ),
            Color(
                Random.nextInt(255),
                Random.nextInt(255),
                Random.nextInt(255),
                255
            ),
            Color(
                Random.nextInt(255),
                Random.nextInt(255),
                Random.nextInt(255),
                255
            )
        )
    }

    var gradientColors by remember {
        mutableStateOf(changeColor())
    }

    var pointerOffset by remember {
        mutableStateOf(Offset(0f, 0f))
    }

    if(reset){
        circles.clear()
        erasedPoints.reset()
        gradientColors = changeColor()
        resetCanvas()
    }

    Box(modifier = Modifier
        .fillMaxSize()
        .clipToBounds()
        .pointerInput("dragging") {
            detectDragGestures(onDragStart = {
                pointerOffset = it
                erasedPoints.moveTo(pointerOffset.x, pointerOffset.y)
                circles.add(pointerOffset)
            }) { change, dragAmount ->
                pointerOffset += dragAmount
                erasedPoints.lineTo(pointerOffset.x, pointerOffset.y)
                circles.add(pointerOffset)

            }
        }
        .drawBehind {
            drawRect(
                brush = Brush.linearGradient(
                    colors = gradientColors,
                    start = Offset.Zero,
                    end = Offset.Infinite
                )
            )
        }
        .drawWithContent {
            drawRect(Color.Black)
            circles.forEach {
                drawCircle(
                    brush = Brush.linearGradient(
                        colors = gradientColors,
                        start = Offset.Zero,
                        end = Offset.Infinite
                    ), center = it,
                    radius = 25f,
                    blendMode = BlendMode.SrcIn
                )
            }
            if (!erasedPoints.isEmpty) {
                drawPath(
                    path = erasedPoints,
                    brush = Brush.linearGradient(
                        colors = gradientColors,
                        start = Offset.Zero,
                        end = Offset.Infinite
                    ),
                    style = Stroke(
                        width = 50f,
                        cap = StrokeCap.Round,
                        join = StrokeJoin.Round
                    ),
                    blendMode = BlendMode.SrcIn
                )
            }
        }) {

    }
}

}

让我困惑的部分是:

drawRect(Color.Black)
        circles.forEach {
            drawCircle(
                brush = Brush.linearGradient(
                    colors = gradientColors,
                    start = Offset.Zero,
                    end = Offset.Infinite
                ), center = it,
                radius = 25f,
                blendMode = BlendMode.SrcIn
            )
        }
        if (!erasedPoints.isEmpty) {
            drawPath(
                path = erasedPoints,
                brush = Brush.linearGradient(
                    colors = gradientColors,
                    start = Offset.Zero,
                    end = Offset.Infinite
                ),
                style = Stroke(
                    width = 50f,
                    cap = StrokeCap.Round,
                    join = StrokeJoin.Round
                ),
                blendMode = BlendMode.SrcIn
            )
        }

Successful

图像显示了想要的结果,但只有当我添加drawPath并循环drawCircle方法时它才有效。

如果我只保留drawPath并删除drawCircle循环(我想这就是所需要的),那么屏幕上什么也不会发生。只是黑色。

如果我在drawCircle中将混合模式更改为BlendMode.CLEAR,我会得到以下结果:

Weird output

两种方法似乎都没有必要。我想我所需要的只是drawPath。 任何信息或指导将不胜感激。

android path android-jetpack-compose android-canvas draw
1个回答
0
投票

您可以用圆圈观察到的问题是在绘制过程中没有读取绘图范围内的可观察值或状态。你的路径不会改变,只有它的内在属性改变。

如果您检查有关如何使用触摸事件在画布上绘制的答案,您可以看到

MutableState
的位置被传递到Canvas的drawScope以在每个位置更改时触发绘制阶段。

您可以像这样更改您的可组合项

@Composable
private fun Sample(
    reset: Boolean,
    resetCanvas: () -> Unit,
) {

    val erasedPoints by remember {
        mutableStateOf(Path())
    }

    var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }

    fun changeColor(): List<Color> {
        return listOf(
            Color(
                Random.nextInt(255),
                Random.nextInt(255),
                Random.nextInt(255),
                255
            ),
            Color(
                Random.nextInt(255),
                Random.nextInt(255),
                Random.nextInt(255),
                255
            ),
            Color(
                Random.nextInt(255),
                Random.nextInt(255),
                Random.nextInt(255),
                255
            )
        )
    }

    var gradientColors by remember {
        mutableStateOf(changeColor())
    }

    var pointerOffset by remember {
        mutableStateOf(Offset.Unspecified)
    }

    if (reset) {
        erasedPoints.reset()
        gradientColors = changeColor()
        resetCanvas()
    }

    Box(modifier = Modifier
        .fillMaxSize()
        .clipToBounds()
        .pointerInput("dragging") {
            detectDragGestures(
                onDragCancel = {
                    motionEvent = MotionEvent.Up
                    pointerOffset = Offset.Unspecified

                },
                onDragEnd = {
                    motionEvent = MotionEvent.Up
                    pointerOffset = Offset.Unspecified
                },
                onDragStart = {
                    motionEvent = MotionEvent.Down
                    pointerOffset = it
                    erasedPoints.moveTo(pointerOffset.x, pointerOffset.y)
                }
            ) { change, dragAmount ->
                pointerOffset += dragAmount
                motionEvent = MotionEvent.Move
            }
        }
        .drawBehind {
            drawRect(
                brush = Brush.linearGradient(
                    colors = gradientColors,
                    start = Offset.Zero,
                    end = Offset.Infinite
                )
            )
        }

        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithContent {
            drawRect(Color.Black)

           if (
                motionEvent == MotionEvent.Move &&
                pointerOffset != Offset.Unspecified
            ) {
                erasedPoints.lineTo(pointerOffset.x, pointerOffset.y)
            }

            if (erasedPoints.isEmpty.not()) {
                drawPath(
                    path = erasedPoints,
                    color = Color.Transparent,
                    style = Stroke(
                        width = 50f,
                        cap = StrokeCap.Round,
                        join = StrokeJoin.Round
                    ),
                    blendMode = BlendMode.Clear
                )
            }
        }) {
    }
}

第二个问题是应用混合模式从黑色背景中擦除。为了使混合模式正常工作,您需要设置合成策略或屏幕外缓冲区。

Jetpack Compose 将 PorterDuffMode 应用于图像

并将混合模式应用于透明路径以去除黑色,同时创建类似划痕的效果,在黑色矩形下方显示渐变。

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.