5

I'm trying to create a rounded triangle using Canvas in Jetpack Compose.

I try this code for drawing triangle:

@Composable
fun RoundedTriangle() {
    Canvas(modifier = Modifier.size(500.dp)) {
        val trianglePath = Path().apply {
            val height = size.height
            val width = size.width
            moveTo(width / 2.0f, 0f)
            lineTo(width, height)
            lineTo(0f, height)
        }
            drawPath(trianglePath, color = Color.Blue)
    }
}

But I don't know how to round the triangle corners. I also tried to use arcTo, but I was unable to get a suitable result.

How can I draw something like the figure below?

enter image description here

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Mohammad Derakhshan
  • 1,262
  • 11
  • 33

2 Answers2

10

For Stroke you can specify rounding like this:

drawPath(
    ...
    style = Stroke(
        width = 2.dp.toPx(),
        pathEffect = PathEffect.cornerPathEffect(4.dp.toPx())
    )
)

Yet Fill seems lack of support rounding. I've created a feature request, please star it.

But Canvas has drawOutline function, which accepts both Outline, which can wrap a Path, and Paint, for which you can specify pathEffect:

Canvas(modifier = Modifier.fillMaxWidth().aspectRatio(1f)) {
    val rect = Rect(Offset.Zero, size)
    val trianglePath = Path().apply {
        moveTo(rect.topCenter)
        lineTo(rect.bottomRight)
        lineTo(rect.bottomLeft)
        close()
    }

    drawIntoCanvas { canvas ->
        canvas.drawOutline(
            outline = Outline.Generic(trianglePath),
            paint = Paint().apply {
                color = Color.Black
                pathEffect = PathEffect.cornerPathEffect(rect.maxDimension / 3)
            }
        )
    }
}

Path helpers:

fun Path.moveTo(offset: Offset) = moveTo(offset.x, offset.y)
fun Path.lineTo(offset: Offset) = lineTo(offset.x, offset.y)

Result:

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • do you know how can this be applied to a square? – AouledIssa Feb 03 '22 at 01:53
  • 1
    @AouledIssa just add one more point to the path, for example like [this](https://gist.github.com/PhilipDukhov/57a3a0e9635a5f3157380227db259ffc) – Phil Dukhov Feb 03 '22 at 02:22
  • Thanks for the answer! However that will render a square with a 45° rotation since you're anchoring the edge centers. I could achieve the correct result using the edges but also **closing** the path. – AouledIssa Feb 03 '22 at 02:55
  • @AouledIssa if you need a plain square, not rotated, you simply can use `DrawScope.drawRoundRect` – Phil Dukhov Feb 03 '22 at 02:58
  • true but that will not give you the effect of `super circle` or smoothed rounded edges. – AouledIssa Feb 03 '22 at 03:00
  • @AouledIssa agree that it looks cleaner with `close()` – Phil Dukhov Feb 03 '22 at 03:11
  • Is there a way to draw a single rounded corner? Or should I open a different question for that? – Raymond Arteaga Jun 16 '22 at 03:28
  • 1
    @RaymondArteaga Yes, it's a different question. I guess you can draw it by hand, e.g. create `Path` with `addArc`, take [this answer](https://stackoverflow.com/a/69323918/3585796) as a reference – Phil Dukhov Jun 16 '22 at 04:25
1

Based on @philip-dukhov answer, if anyone is interested in appliying this to a square

@Composable
fun SquirclePath(
    modifier: Modifier,
    smoothingFactor: Int = 60,
    color: Color,
    strokeWidth: Float,
) {
    Canvas(
        modifier = modifier
    ) {
        val rect = Rect(Offset.Zero, size)
        val percent = smoothingFactor.percentOf(rect.minDimension)
        val squirclePath = Path().apply {
            with(rect) {
                lineTo(topRight)
                lineTo(bottomRight)
                lineTo(bottomLeft)
                lineTo(topLeft)
                // this is where the path is finally linked together
                close()
            }
        }

        drawIntoCanvas { canvas ->
            canvas.drawOutline(
                outline = Outline.Generic(squirclePath),
                paint = Paint().apply {
                    this.color = color
                    this.style = PaintingStyle.Fill
                    this.strokeWidth = strokeWidth
                    pathEffect = PathEffect.cornerPathEffect(percent)
                }
            )
        }
    }
}

fun Int.percentOf(target:Float) = (this.toFloat() / 100) * target
AouledIssa
  • 2,528
  • 2
  • 22
  • 39