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) }}@Composablefun 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.
Pick your shapes
Section titled “Pick your shapes”Shapes with similar complexity morph most smoothly. Abstract shapes belong on images, avatars and decoration, not on containers holding text.
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.