Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack-compose-canvasjetpack-compose-drawscope

how to create a hexagon shape with curved endsin Jetpack composeH


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
                            )
                        }
                    }
            )
        }
    }