how to create a hexagon shape with curved endsin Jetpack composeH

Issue

I’ve attempted to create a hexagon shape in jetpack compose but could only achieve pointed ends, tried several methods with quadraticBezierTo and arcs but couldn’t achieve a result with curved ends.

I’ve tried to look on the web but not quite what i wanted, If you have an idea how to achieve it, thanks for answering to this post.

Here’s the current code without curved ends:

class HexagonShape : Shape {

    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        return Outline.Generic(
            path = drawCustomHexagonPath(size)
        )
    }
}

private fun drawCustomHexagonPath(size: Size): Path {
    return Path().apply {
        val radius = min(size.width / 2f, size.height / 2f)
        val triangleHeight = (sqrt(3.0) * radius / 2)
        val centerX = size.width / 2
        val centerY = size.height / 2

        moveTo(x = centerX, y = centerY + radius)

        lineTo(x = (centerX - triangleHeight).toFloat(), y = centerY + radius / 2)
        lineTo(x = (centerX - triangleHeight).toFloat(), y = centerY - radius / 2)
        lineTo(x = centerX, y = centerY - radius)
        lineTo(x = (centerX + triangleHeight).toFloat(), y = centerY - radius / 2)
        lineTo(x = (centerX + triangleHeight).toFloat(), y = centerY + radius / 2)

        close()
    }
}

@Composable
fun Hexagon(modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .clip(HexagonShape())
            .background(md_theme_light_inversePrimary)
            .width(500.dp)
            .height(500.dp),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = painterResource(id = R.drawable.app_bg),
            contentDescription = "My Hexagon Image",
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .wrapContentSize()
                .background(color = Color.Cyan)
        )
    }
}

Solution

You can use pathEffect = PathEffect.cornerPathEffect(cornerRadius)

enter image description here

@Composable
private fun DrawPolygonPath() {
    var sides by remember { mutableStateOf(3f) }
    var cornerRadius by remember { mutableStateOf(1f) }

    Canvas(modifier = canvasModifier) {
        val canvasWidth = size.width
        val canvasHeight = size.height
        val cx = canvasWidth / 2
        val cy = canvasHeight / 2
        val radius = (canvasHeight - 20.dp.toPx()) / 2
        val path = createPolygonPath(cx, cy, sides.roundToInt(), radius)

        drawPath(
            color = Color.Red,
            path = path,
            style = Stroke(
                width = 4.dp.toPx(),
                pathEffect = PathEffect.cornerPathEffect(cornerRadius)
            )
        )
    }

    Column(modifier = Modifier.padding(horizontal = 20.dp)) {
        Text(text = "Sides ${sides.roundToInt()}")
        Slider(
            value = sides,
            onValueChange = { sides = it },
            valueRange = 3f..12f,
            steps = 10
        )

        Text(text = "CornerRadius ${cornerRadius.roundToInt()}")

        Slider(
            value = cornerRadius,
            onValueChange = { cornerRadius = it },
            valueRange = 0f..50f,
        )
    }
}

and polygon function

fun createPolygonPath(cx: Float, cy: Float, sides: Int, radius: Float): Path {
    val angle = 2.0 * Math.PI / sides

    return Path().apply {
        moveTo(
            cx + (radius * cos(0.0)).toFloat(),
            cy + (radius * sin(0.0)).toFloat()
        )
        for (i in 1 until sides) {
            lineTo(
                cx + (radius * cos(angle * i)).toFloat(),
                cy + (radius * sin(angle * i)).toFloat()
            )
        }
        close()
    }
}

You can get ImageBitmap from Coil and you can draw it with BlendMode as like this

enter image description here

@Preview
@Composable
fun ImageTest() {
    Column(
        modifier = Modifier.fillMaxSize().padding(32.dp)
    ) {
        val imageBitmap = ImageBitmap.imageResource(R.drawable.avatar_1_raster)

        Box(
            Modifier
                .size(200.dp)
                .border(2.dp, Color.Green)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.Offscreen
                }
                .drawWithCache {
                    val canvasWidth = size.width
                    val canvasHeight = size.height
                    val cx = canvasWidth / 2
                    val cy = canvasHeight / 2
                    val radius = canvasHeight / 4
                    val path = createPolygonPath(cx, cy, 6, radius)

                    onDrawWithContent {

                        // Source
                        drawPath(
                            path = path,
                            color = Color.Red,
                            style = Stroke(
                                width = canvasHeight / 2,
                                pathEffect = PathEffect.cornerPathEffect(15f)
                            )
                        )

                        drawCircle(
                            color = Color.Red,
                            radius = 10f
                        )

                        // Destination
                        drawImage(
                            image = imageBitmap,
                            dstSize = IntSize(size.width.toInt(), size.height.toInt()),
                            blendMode = BlendMode.SrcIn
                        )
                    }
                }
        )
    }
}

Answered By – Thracian

Answer Checked By – Willingham (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *