Search code examples
androidkotlinandroid-jetpack-composeandroid-graphics

How to place Image in an arc in jetpack compose?


Following is my code for ticket like background.I used multiple sources on google to achieve this

    class TicketShapeBackground(
    private val circleRadius: Dp,
    private val cornerSize: CornerSize
) : Shape {

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

    private fun getPath(size: Size, density: Density): Path {
        val roundedRect = RoundRect(size.toRect(), CornerRadius(cornerSize.toPx(size, density)))
        val roundedRectPath = Path().apply { addRoundRect(roundedRect) }
        return Path.combine(operation = PathOperation.Intersect, path1 = roundedRectPath, path2 = getTicketPath(size, density))
    }

    private fun getTicketPath(size: Size, density: Density): Path {
        val middleX = size.width.div(other = 2)
        val middleY = size.height.div(other = 2)
        val circleRadiusInPx = with(density) { circleRadius.toPx() }
        return Path().apply {
            reset()
           
            lineTo(x = 0F, y = 0F)
       
            lineTo(middleX, y = 0F)


           
            arcTo(
                rect = Rect(
                    left = middleX.minus(circleRadiusInPx),
                    top = 0F.minus(circleRadiusInPx),
                    right = middleX.plus(circleRadiusInPx),
                    bottom = circleRadiusInPx
                ),
                startAngleDegrees = 180F,
                sweepAngleDegrees = -180F,
                forceMoveTo = false
            )
            // Draw line to top right
            lineTo(x = size.width, y = 0F)
           
            lineTo(x = size.width, y = middleY)


           // lineTo(x = size.width, y = 0F)

           // lineTo(x = size.width, y = middleY)

            arcTo(
                rect = Rect(
                    left = size.width - circleRadiusInPx,
                    top = middleY - circleRadiusInPx,
                    right = size.width + circleRadiusInPx,
                    bottom = middleY + circleRadiusInPx
                ),
                startAngleDegrees = 270f,
                sweepAngleDegrees = -180F,
                forceMoveTo = false
            )
            lineTo(x = size.width, y = size.height)


          
            lineTo(middleX, y = size.height)
           
            lineTo(x = 0F, y = size.height)


            lineTo(x = size.width, y = size.height)

            lineTo(0F, size.height)

            lineTo(x = 0F, y = size.height)
            arcTo(
                rect = Rect(
                    left = 0F - circleRadiusInPx,
                    top = middleY - circleRadiusInPx,
                    right = circleRadiusInPx,
                    bottom = middleY + circleRadiusInPx
                ),
                startAngleDegrees = 90f,
                sweepAngleDegrees = -180F,
                forceMoveTo = false
            )

            lineTo(x = 0F, y = 0F)
        }
    }
}

and use it like

 Box(
                Modifier.clip(TicketShapeBackground(34.dp, CornerSize(5.dp)))

                    .fillMaxSize()
                    .background(Color.Red)
            ) { }

Now the arc at the top ie after lineTo(middleX, y = 0F) I need to place an image inside the arc,like a circle image. How to achieve it. One way could be using a Box maybe but not sure how to do it, also I am not sure if it is a clean solution. Any help is appreciated .Thanks


Solution

  • Is this what you need?

    enter image description here

    val radius = 34.dp
    Box(Modifier.background(Color.White)) {
        Image(
            imageVector = Icons.Default.Face,
            modifier = Modifier
                .align(Alignment.TopCenter)
                .offset(y = -radius)
                .size(radius * 2),
            contentDescription = "top arc image",
        )
        Box(
            Modifier
                .clip(TicketShapeBackground(radius, CornerSize(5.dp)))
                .fillMaxSize()
                .background(Color.Red)
        )
    }