Search code examples
kotlinandroid-jetpack-composeandroid-canvasandroid-jetpack

How can to create a curved Text Button


I'm beginner in jetpack compose, especially in Canvas.

I want to create this with Canvas:

enter image description here

But I don't know how. I could create curved Text but with fillMaxSize and it's not useful for me. Because I want to use this picture as a button in a page and I have some buttons like this one in one page. So I want to have this Canvas with wrapcontent size. So how can I create that?


Solution

  • The answer mentioned in the comments above is mine. I did some adjustments in order to give you some direction. The code is listed below:

    @Composable
    fun ButtonWithCurvedText(
        @DrawableRes icon: Int,
        text: String,
        iconSize: Dp,
        onClick: () -> Unit, 
    ) {
        val density = LocalDensity.current
        val imgSize = iconSize
        val imageSizePx = with(density) { imgSize.toPx() }
        val labelSize = 12.sp
        val textToIconPadding = 4.dp
        val textToIconPaddingPx = with(density) { textToIconPadding.toPx() }
        val boxSize = imgSize + (textToIconPadding * 2) + with(density) { labelSize.roundToPx().toDp() }
        val boxSizePx = with(density) { boxSize.toPx() }
    
        val imageInset = ((boxSize - imgSize) / 2)
        val imageInsetPx = with(density) { imageInset.toPx() }
    
        val arcTop = imageInsetPx - (textToIconPaddingPx / 2f)
        val arcLeft = imageInsetPx - (textToIconPaddingPx / 2f)
        val arcBottom = boxSizePx - imageInsetPx + (textToIconPaddingPx / 2f)
        val arcRight = boxSizePx - imageInsetPx + (textToIconPaddingPx / 2f)
    
        val vectorPainter = rememberVectorPainter(
            ImageVector.vectorResource(icon)
        )
        Canvas(
            modifier = Modifier
                .size(boxSize)
                .background(Color.Gray)
                .clickable {
                    onClick()
                }
        ) {
            drawCircle(Color.LightGray, radius = imageSizePx / 2f)
            with(vectorPainter) {
                inset(
                    horizontal = imageInsetPx,
                    vertical = imageInsetPx
                ) {
                    draw(Size(imageSizePx, imageSizePx))
                }
            }
            drawIntoCanvas {
                val path = Path().apply {
                    addArc(arcLeft, arcTop, arcRight, arcBottom, 270f, 180f)
                }
                it.nativeCanvas.drawTextOnPath(
                    text,
                    path,
                    0f,
                    0f,
                    Paint().apply {
                        textSize = labelSize.toPx()
                        textAlign = Paint.Align.CENTER
                    }
                )
            }
        }
    }
    

    As you mentioned, you want something responsive. You just need to make some constants used here as parameters. The only "size" param required here is the icon size. You can use like this:

    Row() {
        ButtonWithCurvedText(R.drawable.ic_share, "Share", 48.dp, {})
        ButtonWithCurvedText(R.drawable.ic_cancel, "Cancel", 72.dp, {})
        ButtonWithCurvedText(R.drawable.ic_check, "Check", 96.dp, {})
    }
    

    The result is like below:

    enter image description here