Instantly share code, notes, and snippets.
Created
July 6, 2025 15:11
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save remziakgoz/fd6ed82180aa57694aa75feb5d5c244d to your computer and use it in GitHub Desktop.
Jetpack Compose animation demo of a molecular-style radial menu with rotating buttons, directional detail popups, and interactive motion using Animatable, LaunchedEffect, and Offset geometry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Composable | |
| fun MolekulerMenu() { | |
| var center by remember { mutableStateOf(Offset.Zero) } | |
| var expanded by remember { mutableStateOf(false) } | |
| var selectedIndex by remember { mutableStateOf<Int?>(null) } | |
| val radius = remember { Animatable(0f) } | |
| val rotation = remember { Animatable(0f) } | |
| LaunchedEffect(center) { | |
| if (expanded) { | |
| radius.snapTo(0f) | |
| radius.animateTo(150f, tween(500)) | |
| } | |
| } | |
| LaunchedEffect(expanded) { | |
| if (expanded) { | |
| while (true) { | |
| rotation.animateTo( | |
| rotation.value + 360f, | |
| animationSpec = tween(durationMillis = 8000, easing = LinearEasing) | |
| ) | |
| } | |
| } | |
| } | |
| BackHandler(enabled = expanded || selectedIndex != null) { | |
| if (selectedIndex != null) { | |
| selectedIndex = null | |
| } else { | |
| expanded = false | |
| } | |
| } | |
| Box( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .background(Color(0xFF101010)) | |
| .pointerInput(Unit) { | |
| detectTapGestures { offset -> | |
| if (!expanded) { | |
| center = offset | |
| expanded = true | |
| } | |
| } | |
| } | |
| ) { | |
| if (!expanded) { | |
| Text( | |
| "TAP", | |
| color = Color.White, | |
| fontSize = 24.sp, | |
| modifier = Modifier | |
| .align(Alignment.Center) | |
| .graphicsLayer { | |
| alpha = ((sin(System.currentTimeMillis() / 300.0) + 1.0) / 2.0).toFloat() | |
| } | |
| ) | |
| } | |
| AnimatedVisibility( | |
| visible = selectedIndex != null, | |
| enter = fadeIn(tween(300)) + scaleIn(), | |
| exit = fadeOut(tween(300)) + scaleOut() | |
| ) { | |
| selectedIndex?.let { | |
| Box( | |
| modifier = Modifier | |
| .align(Alignment.Center) | |
| .size(200.dp) | |
| .background(Color(0xFF222222), shape = CircleShape), | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Column(horizontalAlignment = Alignment.CenterHorizontally) { | |
| Text( | |
| text = "Detail: ${it + 1}", | |
| color = Color.Yellow, | |
| fontSize = 24.sp | |
| ) | |
| Spacer(modifier = Modifier.height(16.dp)) | |
| Button(onClick = { selectedIndex = null }) { | |
| Text("Back") | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (expanded) { | |
| Box( | |
| modifier = Modifier | |
| .size(60.dp) | |
| .offset { IntOffset(center.x.toInt() - 45, center.y.toInt() - 45) } | |
| .background(Color.Cyan, CircleShape) | |
| ) | |
| val count = 6 | |
| repeat(count) { index -> | |
| val baseAngle = (2 * PI / count) * index | |
| val dynamicAngle = baseAngle + Math.toRadians(rotation.value.toDouble()) | |
| val x = center.x + radius.value * cos(dynamicAngle) | |
| val y = center.y + radius.value * sin(dynamicAngle) | |
| val isVisible = selectedIndex == null || selectedIndex == index | |
| AnimatedVisibility( | |
| visible = isVisible, | |
| enter = fadeIn(tween(300)) + scaleIn(), | |
| exit = fadeOut(tween(300)) + scaleOut() | |
| ) { | |
| val isSelected = selectedIndex == index | |
| Box( | |
| modifier = Modifier | |
| .size(if (isSelected) 60.dp else 40.dp) | |
| .offset { IntOffset(x.toInt() - 20, y.toInt() - 20) } | |
| .background( | |
| color = if (isSelected) Color.Yellow else Color.Magenta, | |
| shape = CircleShape | |
| ) | |
| .clickable { | |
| selectedIndex = index | |
| }, | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Text( | |
| text = "${index + 1}", | |
| color = Color.White, | |
| fontSize = if (isSelected) 20.sp else 16.sp | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment