Search code examples
androidandroid-jetpack-composeandroid-canvas

How can I draw transparent circles with gradient edges into a shaded overlay in android compose?


For example if in an onboarding flow you wanted to darken the rest of the screen and then draw a light circle around some part of the screen to highlight something like a spotlight how would you do it? The circle needs to have gradient edges. It also needs to at least be compatible with jetpack compose.

This is my attempt.

  1. Draw a dark overlay that is partly transparent.
  2. Draw a circle without the gradient edges to punch through the overlay using BlendMode.Clear
  3. Draw transparent circle with gradient edge that goes back to the same color as background.
// Background content, in my real use case this would be something else
        Image(
            modifier = Modifier.matchParentSize(),
            painter = painterResource(id = R.drawable.hqdefault), contentDescription = "")

        val overlayShadeColor = Color.Black.copy(alpha = 0.8f)
        val gradient = Brush.radialGradient(
            .2f to Color.Transparent,
            .5f to overlayShadeColor)
        val radius = 120.dp

        Canvas(modifier = Modifier.fillMaxSize().graphicsLayer {
            alpha = .99f
        }) {

            // Dark overlay
            drawRect(overlayShadeColor)

            // Circle to punch through the overlay
            drawCircle(
                color = Color.White,
                radius = radius.toPx(),
                blendMode = BlendMode.Clear
            )

            // Circle that adds back a gradient edge
            drawCircle(
                gradient,
                radius = radius.toPx(),
            )
        }

It looks like this: enter image description here

The flaw with my attempt is that it leaves a very small (probably 1 hardware pixel wide) line on the edge where the circles meet with the background. Maybe canvas is not the right approach and there is an easier way to do this. I am open to suggestions.


Solution

  • The punch hole is a bit overkill. SRC Blend will work the same

    
            drawRect(overlayShadeColor)
    
            drawCircle(
                gradient,
                radius = radius.toPx(),
                blendMode = BlendMode.Src
            )