Jetpack Compose中的Canvas

本文详细介绍了Jetpack Compose中Canvas的使用,包括基本图形绘制如线条、圆形、矩形等,图片绘制,混合模式,文本绘制,Canvas变换如旋转、缩放、移动等,以及结合手势事件进行Path绘制和实现系统Ripple效果。同时探讨了Canvas在事件检测中的挑战和解决方案,以及GraphicsLayer的应用,展示了如何设置偏移、旋转和缩放等效果。
摘要由CSDN通过智能技术生成

Jetpack Compose中的Canvas API 使用起来感觉比传统View中的要简单一些,因为它不需要画笔Paint和画布分开来,大多数直接就是一个函数搞定,当然也有一些限制。

Compose 直接提供了一个叫 CanvasComposable 组件,可以在任何 Composable 组件中直接使用,在 CanvasDrawScope作用域中就可以使用其提供的各种绘制Api进行绘制了。这比传统View要方便的多,传统View中,你只能继承一个View控件,才有机会覆写其onDraw()方法。

基本图形绘制

常用的API一览表:

API 描述
drawLine 绘制一条线
drawRect 绘制一个矩形
drawImage 绘制一张图片
drawRoundRect 绘制一个圆角矩形
drawCircle 绘制一个圆
drawOval 绘制一个椭圆
drawArc 绘制一条弧线
drawPath 绘制一条路径
drawPoints 绘制一些点

这些基本图形的绘制比较简单,基本上尝试一下就知道如何使用了。Compose中的Canvas坐标体系跟传统View一样,也是也左上角为坐标原点的,因此如果是设置偏移量都是针对Canvas左上角而言的。

drawLine
@Composable
fun DrawLineExample() {
   
    TutorialText2(text = "strokeWidth")
    Canvas(modifier = canvasModifier) {
   
        drawLine(
            start = Offset(x = 100f, y = 30f),
            end = Offset(x = size.width - 100f, y = 30f),
            color = Color.Red,
        )

        drawLine(
            start = Offset(x = 100f, y = 70f),
            end = Offset(x = size.width - 100f, y = 70f),
            color = Color.Red,
            strokeWidth = 5f
        )

        drawLine(
            start = Offset(x = 100f, y = 110f),
            end = Offset(x = size.width - 100f, y = 110f),
            color = Color.Red,
            strokeWidth = 10f
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "StrokeCap")
    Canvas(modifier = canvasModifier) {
   

        drawLine(
            cap = StrokeCap.Round,
            start = Offset(x = 100f, y = 30f),
            end = Offset(x = size.width - 100f, y = 30f),
            color = Color.Red,
            strokeWidth = 20f
        )

        drawLine(
            cap = StrokeCap.Butt,
            start = Offset(x = 100f, y = 70f),
            end = Offset(x = size.width - 100f, y = 70f),
            color = Color.Red,
            strokeWidth = 20f
        )

        drawLine(
            cap = StrokeCap.Square,
            start = Offset(x = 100f, y = 110f),
            end = Offset(x = size.width - 100f, y = 110f),
            color = Color.Red,
            strokeWidth = 20f
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "Brush")
    Canvas(modifier = canvasModifier) {
   

        drawLine(
            brush = Brush.linearGradient(
                colors = listOf(Color.Red, Color.Green)
            ),
            start = Offset(x = 100f, y = 30f),
            end = Offset(x = size.width - 100f, y = 30f),
            strokeWidth = 20f,
        )

        drawLine(
            brush = Brush.radialGradient(
                colors = listOf(Color.Red, Color.Green, Color.Blue)
            ),
            start = Offset(x = 100f, y = 70f),
            end = Offset(x = size.width - 100f, y = 70f),
            strokeWidth = 20f,
        )

        drawLine(
            brush = Brush.sweepGradient(
                colors = listOf(Color.Red, Color.Green, Color.Blue)
            ),
            start = Offset(x = 100f, y = 110f),
            end = Offset(x = size.width - 100f, y = 110f),
            strokeWidth = 20f,
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "PathEffect")
    Canvas(
        modifier = Modifier
            .padding(8.dp)
            .shadow(1.dp)
            .background(Color.White)
            .fillMaxWidth()
            .height(120.dp)
    ) {
   

        drawLine(
            pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f)),
            start = Offset(x = 100f, y = 30f),
            end = Offset(x = size.width - 100f, y = 30f),
            color = Color.Red,
            strokeWidth = 10f
        )


        drawLine(
            pathEffect = PathEffect.dashPathEffect(floatArrayOf(40f, 10f)),
            start = Offset(x = 100f, y = 70f),
            end = Offset(x = size.width - 100f, y = 70f),
            color = Color.Red,
            strokeWidth = 10f
        )


        drawLine(
            pathEffect = PathEffect.dashPathEffect(floatArrayOf(70f, 40f)),
            start = Offset(x = 100f, y = 110f),
            end = Offset(x = size.width - 100f, y = 110f),
            cap = StrokeCap.Round,
            color = Color.Red,
            strokeWidth = 15f
        )

        val path = Path().apply {
   
            moveTo(10f, 0f)
            lineTo(20f, 10f)
            lineTo(10f, 20f)
            lineTo(0f, 10f)
        }

        drawLine(
            pathEffect = PathEffect.stampedPathEffect(
                shape = path,
                advance = 30f,
                phase = 30f,
                style = StampedPathEffectStyle.Rotate
            ),
            start = Offset(x = 100f, y = 150f),
            end = Offset(x = size.width - 100f, y = 150f),
            color = Color.Green,
            strokeWidth = 10f
        )

        drawLine(
            pathEffect = PathEffect.stampedPathEffect(
                shape = path,
                advance = 30f,
                phase = 10f,
                style = StampedPathEffectStyle.Morph
            ),
            start = Offset(x = 100f, y = 190f),
            end = Offset(x = size.width - 100f, y = 190f),
            color = Color.Green,
            strokeWidth = 10f
        )
    }
} 

在这里插入图片描述

drawCircle & drawOval
@Composable
fun DrawCircleExample() {
   
    TutorialText2(text = "Oval and Circle")
    Canvas(modifier = canvasModifier2) {
   

        val canvasWidth = size.width
        val canvasHeight = size.height
        val radius = canvasHeight / 2

        drawOval(
            color = Color.Blue,
            topLeft = Offset.Zero,
            size = Size(1.2f * canvasHeight, canvasHeight)
        )
        drawOval(
            color = Color.Green,
            topLeft = Offset(1.5f * canvasHeight, 0f),
            size = Size(canvasHeight / 1.5f, canvasHeight)
        )
        drawCircle(
            Color.Red,
            center = Offset(canvasWidth - 2 * radius, canvasHeight / 2),
            radius = radius * 0.8f,
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "DrawStyle")

    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val radius = canvasHeight / 2
        val space = (canvasWidth - 6 * radius) / 4

        drawCircle(
            color = Color.Red,
            radius = radius,
            center = Offset(space + radius, canvasHeight / 2),
            style = Stroke(width = 5.dp.toPx())
        )

        drawCircle(
            color = Color.Red,
            radius = radius,
            center = Offset(2 * space + 3 * radius, canvasHeight / 2),
            style = Stroke(
                width = 5.dp.toPx(),
                join = StrokeJoin.Round,
                cap = StrokeCap.Round,
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f))
            )
        )

        val path = Path().apply {
   
            moveTo(10f, 0f)
            lineTo(20f, 10f)
            lineTo(10f, 20f)
            lineTo(0f, 10f)
        }

        val pathEffect = PathEffect.stampedPathEffect(
            shape = path,
            advance = 20f,
            phase = 20f,
            style = StampedPathEffectStyle.Morph
        )

        drawCircle(
            color = Color.Red,
            radius = radius,
            center = Offset(canvasWidth - space - radius, canvasHeight / 2),
            style = Stroke(
                width = 5.dp.toPx(),
                join = StrokeJoin.Round,
                cap = StrokeCap.Round,
                pathEffect = pathEffect
            )
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "Brush")
    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val radius = canvasHeight / 2
        val space = (canvasWidth - 6 * radius) / 4

        drawCircle(
            brush = Brush.linearGradient(
                colors = listOf(Color.Red, Color.Green),
                start = Offset(radius * .3f, radius * .1f),
                end = Offset(radius * 2f, radius * 2f)
            ),
            radius = radius,
            center = Offset(space + radius, canvasHeight / 2),
        )

        drawCircle(
            brush = Brush.radialGradient(
                colors = listOf(Color.Red, Color.Green)
            ),
            radius = radius,
            center = Offset(2 * space + 3 * radius, canvasHeight / 2),
        )

        drawCircle(
            brush = Brush.verticalGradient(
                colors = listOf(
                    Color.Red,
                    Color.Green,
                    Color.Yellow,
                    Color.Blue,
                    Color.Cyan,
                    Color.Magenta
                ),
            ),
            radius = radius,
            center = Offset(canvasWidth - space - radius, canvasHeight / 2)
        )
    }
    Spacer(modifier = Modifier.height(10.dp))
    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val radius = canvasHeight / 2
        val space = (canvasWidth - 6 * radius) / 4

        drawCircle(
            brush = Brush.sweepGradient(
                colors = listOf(
                    Color.Green,
                    Color.Red,
                    Color.Blue
                ),
                center = Offset(space + radius, canvasHeight / 2),
            ),
            radius = radius,
            center = Offset(space + radius, canvasHeight / 2),
        )

        drawCircle(
            brush = Brush.sweepGradient(
                colors = listOf(
                    Color.Green,
                    Color.Cyan,
                    Color.Red,
                    Color.Blue,
                    Color.Yellow,
                    Color.Magenta,
                ),
                // Offset for this gradient is not at center, a little bit left of center
                center = Offset(2 * space + 2.7f * radius, canvasHeight / 2),
            ),
            radius = radius,
            center = Offset(2 * space + 3 * radius, canvasHeight / 2),
        )


        drawCircle(
            brush = Brush.sweepGradient(
                colors = gradientColors,
                center = Offset(canvasWidth - space - radius, canvasHeight / 2),
            ),
            radius = radius,
            center = Offset(canvasWidth - space - radius, canvasHeight / 2)
        )
    }
}

在这里插入图片描述

drawRect
@Composable
private fun DrawRectangleExample() {
   
    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "Rectangle")
    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val space = 60f
        val rectHeight = canvasHeight / 2
        val rectWidth = (canvasWidth - 4 * space) / 3

        drawRect(
            color = Color.Blue,
            topLeft = Offset(space, rectHeight / 2),
            size = Size(rectWidth, rectHeight)
        )

        drawRect(
            color = Color.Green,
            topLeft = Offset(2 * space + rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            style = Stroke(width = 12.dp.toPx())
        )

        drawRect(
            color = Color.Red,
            topLeft = Offset(3 * space + 2 * rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            style = Stroke(width = 2.dp.toPx())
        )
    }

    TutorialText2(text = "RoundedRect")
    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val space = 60f
        val rectHeight = canvasHeight / 2
        val rectWidth = (canvasWidth - 4 * space) / 3

        drawRoundRect(
            color = Color.Blue,
            topLeft = Offset(space, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
        )

        drawRoundRect(
            color = Color.Green,
            topLeft = Offset(2 * space + rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            cornerRadius = CornerRadius(70f, 70f)

        )

        drawRoundRect(
            color = Color.Red,
            topLeft = Offset(3 * space + 2 * rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            cornerRadius = CornerRadius(50f, 25f)
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "DrawStyle")
    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val space = 30f
        val rectHeight = canvasHeight / 2
        val rectWidth = (canvasWidth - 4 * space) / 3

        drawRect(
            color = Color.Blue,
            topLeft = Offset(space, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            style = Stroke(
                width = 2.dp.toPx(),
                join = StrokeJoin.Miter,
                cap = StrokeCap.Butt,
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(15f, 15f))
            )
        )

        drawRect(
            color = Color.Green,
            topLeft = Offset(2 * space + rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            style = Stroke(
                width = 2.dp.toPx(),
                join = StrokeJoin.Bevel,
                cap = StrokeCap.Square,
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(15f, 15f))
            )
        )

        drawRect(
            color = Color.Red,
            topLeft = Offset(3 * space + 2 * rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight),
            style = Stroke(
                width = 2.dp.toPx(),
                join = StrokeJoin.Round,
                cap = StrokeCap.Round,
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(15f, 15f))
            )
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "Brush")
    Canvas(modifier = canvasModifier2) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val space = 30f
        val rectHeight = canvasHeight / 2
        val rectWidth = (canvasWidth - 4 * space) / 3

        drawRect(
            brush = Brush.radialGradient(
                colors = listOf(
                    Color.Green,
                    Color.Red,
                    Color.Blue,
                    Color.Yellow,
                    Color.Magenta
                ),
                center = Offset(space + .5f * rectWidth, rectHeight),
                tileMode = TileMode.Mirror,
                radius = 20f
            ),
            topLeft = Offset(space, rectHeight / 2),
            size = Size(rectWidth, rectHeight)
        )

        drawRect(
            brush = Brush.radialGradient(
                colors = listOf(
                    Color.Green,
                    Color.Red,
                    Color.Blue,
                    Color.Yellow,
                    Color.Magenta
                ),
                center = Offset(2 * space + 1.5f * rectWidth, rectHeight),
                tileMode = TileMode.Repeated,
                radius = 20f
            ),
            topLeft = Offset(2 * space + rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight)
        )

        drawRect(
            brush = Brush.radialGradient(
                colors = listOf(
                    Color.Green,
                    Color.Red,
                    Color.Blue,
                    Color.Yellow,
                    Color.Magenta
                ),
                center = Offset(3 * space + 2.5f * rectWidth, rectHeight),
                tileMode = TileMode.Decal,
                radius = rectHeight / 2
            ),
            topLeft = Offset(3 * space + 2 * rectWidth, rectHeight / 2),
            size = Size(rectWidth, rectHeight)
        )
    }
}

在这里插入图片描述

drawPoints
@Composable
fun DrawPointsExample() {
   
    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "PointMode")
    Canvas(modifier = canvasModifier2) {
   

        val middleW = size.width / 2
        val middleH = size.height / 2
        drawLine(Color.Gray, Offset(0f, middleH), Offset(size.width - 1, middleH))
        drawLine(Color.Gray, Offset(middleW, 0f), Offset(middleW, size.height - 1))

        val points1 = getSinusoidalPoints(size)

        drawPoints(
            color = Color.Blue,
            points = points1,
            cap = StrokeCap.Round,
            pointMode = PointMode.Points,
            strokeWidth = 10f
        )

        val points2 = getSinusoidalPoints(size, 100f)
        drawPoints(
            color = Color.Green,
            points = points2,
            cap = StrokeCap.Round,
            pointMode = PointMode.Lines,
            strokeWidth = 10f
        )

        val points3 = getSinusoidalPoints(size, 200f)
        drawPoints(
            color = Color.Red,
            points = points3,
            cap = StrokeCap.Round,
            pointMode = PointMode.Polygon,
            strokeWidth = 10f
        )
    }

    Spacer(modifier = Modifier.height(10.dp))
    TutorialText2(text = "Brush")
    Canvas(modifier = canvasModifier2) {
   

        val middleW = size.width / 2
        val middleH = size.height / 2
        drawLine(Color.Gray, Offset(0f, middleH), Offset(size.width - 1, middleH))
        drawLine(Color.Gray, Offset(middleW, 0f), Offset(middleW, size.height - 1))


        val points1 = getSinusoidalPoints(size)

        drawPoints(
            brush = Brush.linearGradient(
                colors = listOf(Color.Red, Color.Green)
            ),
            points = points1,
            cap = StrokeCap.Round,
            pointMode = PointMode.Points,
            strokeWidth = 10f
        )

        val points2 = getSinusoidalPoints(size, 100f)
        drawPoints(
            brush = Brush.linearGradient(
                colors = listOf(Color.Green, Color.Magenta)
            ),
            points = points2,
            cap = StrokeCap.Round,
            pointMode = PointMode.Lines,
            strokeWidth = 10f
        )

        val points3 = getSinusoidalPoints(size, 200f)
        drawPoints(
            brush = Brush.linearGradient(
                colors = listOf(Color.Red, Color.Yellow)
            ),
            points = points3,
            cap = StrokeCap.Round,
            pointMode = PointMode.Polygon,
            strokeWidth = 10f
        )
    }
}

fun getSinusoidalPoints(size: Size, horizontalOffset: Float = 0f): MutableList<Offset> {
   
    val points = mutableListOf<Offset>()
    val verticalCenter = size.height / 2

    for (x in 0 until size.width.toInt() step 20) {
   
        val y = (sin(x * (2f * PI / size.width)) * verticalCenter + verticalCenter).toFloat()
        points.add(Offset(x.toFloat() + horizontalOffset, y))
    }
    return points
}

在这里插入图片描述

drawArc
@Composable
fun DrawNegativeArc() {
   
    var startAngle by remember {
    mutableStateOf(0f) }
    var sweepAngle by remember {
    mutableStateOf(60f) }
    var useCenter by remember {
    mutableStateOf(true) }

    Canvas(modifier = canvasModifier) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height

        drawArc(
            color = Red400,
            startAngle,
            sweepAngle,
            useCenter,
            topLeft = Offset((canvasWidth - canvasHeight) / 2, 0f),
            size = Size(canvasHeight, canvasHeight)
        )
    }

    Column(modifier = Modifier.padding(horizontal = 20.dp)) {
   
        Text(text = "StartAngle ${
     startAngle.roundToInt()}")
        Slider(
            value = startAngle,
            onValueChange = {
    startAngle = it },
            valueRange = -180f..180f,
        )

        Text(text = "SweepAngle ${
     sweepAngle.roundToInt()}")
        Slider(
            value = sweepAngle,
            onValueChange = {
    sweepAngle = it },
            valueRange = -180f..180f,
        )

        CheckBoxWithTextRippleFullRow(label = "useCenter", useCenter) {
   
            useCenter = it
        }
    }
}

在这里插入图片描述

在上面的代码中,需要留意的一点是drawArc函数中的startAnglesweepAngle参数,它们的值正值代表的是顺时针方向,而负值代表的是逆时针方向的。

通过多个drawArc绘制饼图:

@Composable
private fun DrawMultipleArcs() {
   
    var startAngleBlue by remember {
    mutableStateOf(0f) }
    var sweepAngleBlue by remember {
    mutableStateOf(120f) }

    var startAngleRed by remember {
    mutableStateOf(120f) }
    var sweepAngleRed by remember {
    mutableStateOf(120f) }

    var startAngleGreen by remember {
    mutableStateOf(240f) }
    var sweepAngleGreen by remember {
    mutableStateOf(120f) }

    var isFill by remember {
    mutableStateOf(true) }

    Canvas(modifier = canvasModifier) {
   
        val canvasWidth = size.width
        val canvasHeight = size.height
        val arcHeight = canvasHeight - 20.dp.toPx()
        val arcStrokeWidth = 10.dp.toPx()
        val style = if (isFill) Fill else Stroke(arcStrokeWidth)

        drawArc(
            color = Blue400,
            startAngleBlue,
            sweepAngleBlue,
            true,
            topLeft = Offset(
                (canvasWidth - canvasHeight) / 2,
                (canvasHeight - arcHeight) / 2
            ),
            size = Size(arcHeight, arcHeight),
            style = style
        )

        drawArc(
            color = Red400,
            startAngleRed,
            sweepAngleRed,
            true,
            topLeft = Offset(
                (canvasWidth - canvasHeight) / 2,
                (canvasHeight - arcHeight) / 2
            ),
            size = Size(arcHeight, arcHeight),
            style = style
        )

        drawArc(
            color = Green400,
            startAngleGreen,
            sweepAngleGreen,
            true,
            topLeft = Offset(
                (canvasWidth - canvasHeight) / 2,
                (canvasHeight - arcHeight) / 2
            ),
            size = Size(arcHeight, arcHeight),
            style = style
        )
    }

    CheckBoxWithTextRippleFullRow(label = "Fill Style", isFill) {
   
        isFill = it
    }

    Column(modifier = Modifier.padding(horizontal = 20.dp)) {
   
        Text(text = "StartAngle ${
     startAngleBlue.roundToInt()}", color = Blue400)
        Slider(
            value = startAngleBlue,
            onValueChange = {
    startAngleBlue = it },
            valueRange = 0f..360f,
        )

        Text(text = "SweepAngle ${
     sweepAngleBlue.roundToInt()}", color = Blue400)
        Slider(
            value = sweepAngleBlue,
            onValueChange = {
    sweepAngleBlue = it },
            valueRange = 0f..360f,
        )
    }


    Column(modifier = Modifier.padding(horizontal = 20.dp)) {
   
        Text(text = "StartAngle ${
     startAngleRed.roundToInt()}", color = Red400)
        Slider(
            value = startAngleRed,
            onValueChange = {
    startAngleRed = it },
            valueRange = 0f..360f,
        )

        Text(text = "SweepAngle ${
     sweepAngleRed.roundToInt()}", color = Red400)
        Slider(
            value = sweepAngleRed,
            onValueChange = {
    sweepAngleRed = it },
            valueRange = 0f..360f,
        )
    }


    Column(modifier = Modifier.padding(horizontal = 20.dp)) {
   
        Text(text = "StartAngle ${
     startAngleGreen.roundToInt()}", color = Green400)
        Slider(
            value = startAngleGreen,
            onValueChange = {
    startAngleGreen = it },
            valueRange = 0f..360f,
        )

        Text(text = "SweepAngle ${
     sweepAngleGreen.roundToInt()}", color = Green400)
        Slider(
            value = sweepAngleGreen,
            onValueChange = {
    sweepAngleGreen = it },
            valueRange = 0f..360f,
        )
    }
}

在这里插入图片描述

drawPath
@Composable
fun DrawPath() {
   
    val path1 = remember {
    Path() }
    val path2 = remember {
    Path() }

    Canvas(modifier = canvasModifier) {
   
        // Since we remember paths from each recomposition we reset them to have fresh ones
        // You can create paths here if you want to have new path instances
        path1.reset()
        path2.reset()

        path1.moveTo(100f, 100f)
        // Draw a line from top right corner (100, 100) to (100,300)
        path1.lineTo(100f, 300f)
        // Draw a line from (100, 300) to (300,300)
        path1.lineTo(300f, 300f)
        // Draw a line from (300, 300) to (300,100)
        path1.lineTo(300f, 100f)
        // Draw a line from (300, 100) to (100,100)
        path1.lineTo(100f, 100f)


        // Using relatives to draw blue path, relative is based on previous position of path
        path2.relativeMoveTo(100f, 100f)
        // Draw a line from (100,100) from (100, 300)
        path2.relativeLineTo(0f, 200f)
        // Draw a line from (100, 300) to (300,300)
        path2.relativeLineTo(200f, 0f)
        // Draw a line from (300, 300) to (300,100)
        path2.relativeLineTo(0f, -200f)
        // Draw a line from (300, 100) to (100,100)
        path2.relativeLineTo(-200f, 0f)

        // Add rounded rectangle to path1
        path1.addRoundRect(
            RoundRect(
                left = 400f,
                top = 200f,
                right = 600f,
                bottom = 400f,
                topLeftCornerRadius = CornerRadius(10f, 10f),
                topRightCornerRadius = CornerRadius(30f, 30f),
                bottomLeftCornerRadius = CornerRadius(50f, 20f),
                bottomRightCornerRadius = CornerRadius(0f, 0f)
            )
        )

        // Add rounded rectangle to path2
        path2.addRoundRect(
            RoundRect(
                left = 700f,
                top = 200f,
                right = 900f,
                bottom = 400f,
                radiusX = 20f,
                radiusY = 20f
            )
        )

        path1.addOval(Rect(left = 400f, top = 50f, right = 500f, bottom = 150f))
        path2.addArc(
            Rect(400f, top = 50f, right = 500f, bottom = 150f),
            startAngleDegrees = 0f,
            sweepAngleDegrees = 180f
        )

        drawPath(
            color = Color.Red,
            path = path1,
            style = Stroke(
                width = 2.dp.toPx(),
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
            )
        )

        drawPath(
            color = Color.Blue,
            path = path2,
            style = Stroke(
                width = 2.dp.toPx(),
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 10f))
            )
        )
    }
}

在这里插入图片描述

path.arcTo
@Composable
fun DrawArcToPath() {
   
    val path1 = remember {
    Path() }
    val path2 = remember {
    Path() }

    var startAngle by remember {
    mutableStateOf(0f) }
    var sweepAngle by remember {
    mutableStateOf(90f) }

    Canvas(modifier = canvasModifier) {
   
        // Since we remember paths from each recomposition we reset them to have fresh ones
        // You can create paths here if you want to have new path instances
        path1.reset()
        path2.reset()

        val rect = Rect(0f, 0f, size.width, size.height)
        path1.addRect(rect)
        path2.arcTo(
            rect,
            startAngleDegrees = startAngle,
            sweepAngleDegrees = sweepAngle,
            forceMoveTo = false
        )

        drawPath(
            color = Color.Red,
            path = path1,
            style = Stroke(
                width = 2.dp.toPx(),
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
            )
        )

        drawPath(
            color = Color.Blue,
            path = path2,
            style = Stroke(width = 2.dp.toPx())
        )
    }

    Column(modifier = Modifier.padding(horizontal = 20.dp)) {
   
        Text(text = "StartAngle ${
     startAngle.roundToInt()}")
        Slider(
            value = startAngle,
            onValueChange = {
    startAngle = it },
            valueRange = -360f..360f,
        )

        Text(text = "SweepAngle ${
     sweepAngle.roundToInt()}")
        Slider(
            value = sweepAngle,
            onValueChange = {
    sweepAngle = it },
            valueRange = -360f..360f,
        )
    }
}

在这里插入图片描述

DrawTicketPath
@Composable
private fun DrawTicketPathWithArc() {
   
    Canvas(modifier = canvasModifier) {
   

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

        // Black background
        val ticketBackgroundWidth = canvasWidth * .8f
        val horizontalSpace = (canvasWidth - ticketBackgroundWidth) / 2

        val ticketBackgroundHeight = canvasHeight * .8f
        val verticalSpace = (canvasHeight - ticketBackgroundHeight) / 2

        // Get ticket path for background
        val path1 = ticketPath(
            topLeft = Offset(horizontalSpace, verticalSpace),
            size = Size(ticketBackgroundWidth, ticketBackgroundHeight),
            cornerRadius = 20.dp.toPx()
        )
        drawPath(path1, color = Color.Black)

        // Dashed path in foreground
        val ticketForegroundWidth = ticketBackgroundWidth * .95f
        val horizontalSpace2 = (canvasWidth - ticketForegroundWidth) / 2

        val ticketForegroundHeight = ticketBackgroundHeight * .9f
        val verticalSpace2 = (canvasHeight - ticketForegroundHeight) / 2

        // Get ticket path for background
        val path2 = ticketPath(
            topLeft = Offset(horizontalSpace2, verticalSpace2),
            size = Size(ticketForegroundWidth, ticketForegroundHeight),
            cornerRadius = 20.dp.toPx()
        )
        drawPath(
            path2,
            color = Color.Red,
            style = Stroke(
                width = 2.dp.toPx(),
                pathEffect = PathEffect.dashPathEffect(
                    floatArrayOf(20f, 20f)
                )
            )
        )
    }
}
/**
 * Create a ticket path with given size and corner radius in px with offset [topLeft].
 *
 * Refer [this link](https://juliensalvi.medium.com/custom-shape-with-jetpack-compose-1cb48a991d42)
 * for implementation details.
 */
fun ticketPath(topLeft: Offset = Offset.Zero, size: Size, cornerRadius: Float): Path {
   
    return Path().apply {
   
        reset()
        // Top left arc
        arcTo(
            rect = Rect(
                left = topLeft.x + -cornerRadius,
                top = topLeft.y + -cornerRadius,
                right = topLeft.x + cornerRadius,
                bottom = topLeft.y + cornerRadius
            ),
            startAngleDegrees = 90.0f,
            sweepAngleDegrees = -90.0f,
            forceMoveTo = false
        )
        lineTo(x = topLeft.x + size.width - cornerRadius, y = topLeft.y)
        // Top right arc
        arcTo(
            rect = Rect(
                left = topLeft.x + size.width - cornerRadius,
                top = topLeft.y + -cornerRadius,
                right = topLeft.x + size.width + cornerRadius,
                bottom = topLeft.y + cornerRadius
            ),
            startAngleDegrees = 180.0f
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值