Instantly share code, notes, and snippets.
Created
November 30, 2024 07:44
-
Star
8
(8)
You must be signed in to star a gist -
Fork
2
(2)
You must be signed in to fork a gist
-
Save ardakazanci/42de6a0c0f65abbac5e3e062afbb8cb4 to your computer and use it in GitHub Desktop.
Slice Menu in Jetpack Compose
This file contains 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 SliceMenu(modifier: Modifier = Modifier, onSliceClick: (Int) -> Unit) { | |
val slices = 6 | |
val colors = listOf( | |
Brush.linearGradient(listOf(Color(0xFFFF1744), Color(0xFFFFC400))), | |
Brush.linearGradient(listOf(Color(0xFF1A237E), Color(0xFF2962FF))), | |
Brush.linearGradient(listOf(Color(0xFF00C853), Color(0xFF64DD17))), | |
Brush.linearGradient(listOf(Color(0xFFFF6D00), Color(0xFFFFAB00))), | |
Brush.linearGradient(listOf(Color(0xFFD500F9), Color(0xFF6200EA))), | |
Brush.linearGradient(listOf(Color(0xFF00BFA5), Color(0xFF00E5FF))) | |
) | |
val degreeStep = 360f / slices | |
var innerRadiusRatio by remember { mutableStateOf(0.3f) } | |
var visibleSlices by remember { mutableStateOf(0) } | |
var sliceSize by remember { mutableStateOf(1.0f) } | |
val coroutineScope = rememberCoroutineScope() | |
val animatedValues = remember { List(slices) { Animatable(0f) } } | |
val sliceClickAnimations = remember { List(slices) { Animatable(1f) } } | |
Column(modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) { | |
Box(modifier = Modifier.weight(1f)) { | |
Canvas( | |
modifier = Modifier | |
.padding(start = 60.dp, end = 60.dp) | |
.fillMaxSize() | |
.pointerInput(true) { | |
detectTapGestures( | |
onTap = { tapOffset -> | |
val center = Offset(x = size.width / 2f, y = size.height / 2f) | |
val dx = tapOffset.x - center.x | |
val dy = tapOffset.y - center.y | |
val distance = hypot(dx, dy) | |
val outerRadius = minOf(center.x, center.y) | |
val innerRadius = outerRadius * innerRadiusRatio | |
if (distance < innerRadius) { | |
coroutineScope.launch { | |
for (i in 0 until slices) { | |
visibleSlices = i + 1 | |
animatedValues[i].animateTo( | |
targetValue = 1.0f, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy) | |
) | |
delay(200) | |
} | |
} | |
} else if (distance > innerRadius && distance < outerRadius && visibleSlices == slices) { | |
val angle = (atan2(dy, dx) * (180 / PI)).toFloat() + 180 | |
val sliceIndex = ((angle / degreeStep).toInt()) % slices | |
onSliceClick(sliceIndex) | |
coroutineScope.launch { | |
sliceClickAnimations[sliceIndex].animateTo( | |
targetValue = 1.2f, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioLowBouncy) | |
) | |
sliceClickAnimations[sliceIndex].animateTo( | |
targetValue = 1f, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioLowBouncy) | |
) | |
} | |
} | |
} | |
) | |
}, | |
onDraw = { | |
val center = Offset(x = size.width / 2f, y = size.height / 2f) | |
val outerRadius = minOf(center.x, center.y) * sliceSize | |
val innerRadius = outerRadius * innerRadiusRatio | |
var startAngle = -90f | |
for (i in 0 until visibleSlices) { | |
val animNormalized = animatedValues[i].value | |
val animatedSweepAngle = animNormalized * degreeStep | |
val scaleValue = sliceClickAnimations[i].value | |
scale(scale = scaleValue, pivot = center) { | |
drawArc( | |
brush = colors[i % colors.size], | |
startAngle = startAngle, | |
sweepAngle = animatedSweepAngle, | |
useCenter = true, | |
topLeft = Offset(center.x - outerRadius, center.y - outerRadius), | |
size = Size( | |
outerRadius * 2, | |
outerRadius * 2 | |
) | |
) | |
drawArc( | |
color = Color.White, | |
startAngle = startAngle, | |
sweepAngle = animatedSweepAngle, | |
useCenter = true, | |
topLeft = Offset(center.x - outerRadius, center.y - outerRadius), | |
size = Size(outerRadius * 2, outerRadius * 2), | |
style = Stroke(width = 10f) | |
) | |
} | |
startAngle += degreeStep | |
} | |
drawCircle( | |
color = Color.Black, | |
radius = innerRadius, | |
center = center | |
) | |
drawCircle( | |
color = Color.White, | |
radius = innerRadius, | |
center = center, | |
style = Stroke(width = 10f) | |
) | |
} | |
) | |
} | |
Slider( | |
value = sliceSize, | |
onValueChange = { sliceSize = it }, | |
valueRange = 0.5f..1.5f, | |
modifier = Modifier.padding(16.dp) | |
) | |
Slider( | |
value = innerRadiusRatio, | |
onValueChange = { innerRadiusRatio = it }, | |
valueRange = 0.1f..0.5f, | |
modifier = Modifier.padding(16.dp) | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment