Skip to content

Instantly share code, notes, and snippets.

@virendersran01
Forked from ardakazanci/SliceMenu.kt
Created December 1, 2024 02:16
Show Gist options
  • Save virendersran01/fdad907b4546c8cae10e9808c3742cca to your computer and use it in GitHub Desktop.
Save virendersran01/fdad907b4546c8cae10e9808c3742cca to your computer and use it in GitHub Desktop.
Slice Menu in Jetpack Compose
@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