Skip to content

Shape morphing in Jetpack Compose

Short answer: build a Morph(MaterialShapes.Circle, MaterialShapes.Cookie9Sided) once, animate a 0..1 progress with a spatial spring, and draw morph.toPath(progress) in a small custom Shape. MaterialShapes and toPath still need @OptIn(ExperimentalMaterial3ExpressiveApi::class).

/** Draws [morph] at [progress] (0 = start polygon, 1 = end polygon), scaled to the layout size. */
class MorphShape(private val morph: Morph, private val progress: Float) : Shape {
override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline {
val path = morph.toPath(progress = progress, path = Path())
path.transform(Matrix().apply { scale(size.width, size.height) })
return Outline.Generic(path)
}
}
@Composable
fun MorphOnTap() {
var selected by remember { mutableStateOf(false) }
val morph = remember { Morph(MaterialShapes.Circle, MaterialShapes.Cookie9Sided) }
val progress by animateFloatAsState(
targetValue = if (selected) 1f else 0f,
animationSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
label = "morph",
)
Box(
Modifier
.size(96.dp)
.background(MaterialTheme.colorScheme.primary, MorphShape(morph, progress))
.clickable { selected = !selected },
)
}

Morph comes from androidx.graphics:graphics-shapes. Add it if your project doesn’t pull it in already.

Shapes with similar complexity morph most smoothly. Abstract shapes belong on images, avatars and decoration, not on containers holding text.

Shapes reference →

Want your agent to get this right every time? Install the skill. If this page helped,star the repo on GitHub.

Built by Sanikant. Set in Google Sans Flex (SIL Open Font License), icons from Material Symbols (Apache 2.0). Material Design is a Google trademark; this project is not affiliated with Google.