Skip to content

Instantly share code, notes, and snippets.

@ardakazanci
Created June 2, 2025 06:54
Show Gist options
  • Save ardakazanci/1297a44276d6a0be06c1227cb446dead to your computer and use it in GitHub Desktop.
Save ardakazanci/1297a44276d6a0be06c1227cb446dead to your computer and use it in GitHub Desktop.
Press L for Love Jetpack Compose
@Composable
fun FloatingHeartsAnimation() {
var showHearts by remember { mutableStateOf(false) }
val heartList = remember { mutableStateListOf<Int>() }
var sliderValue by remember { mutableFloatStateOf(0.5f) }
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
val config = HeartConfig(
radiusMultiplier = lerp(0.5f, 2f, sliderValue),
delayDuration = lerp(100L, 600L, sliderValue)
)
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(RedL)),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Box(contentAlignment = Alignment.Center) {
heartList.forEach { id ->
FloatingHeart(
key = id,
config = config,
onAnimationEnd = { heartList.remove(id) }
)
}
Box(
modifier = Modifier
.size(64.dp)
.graphicsLayer {
scaleX = scale.value
scaleY = scale.value
}
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
scope.launch {
scale.animateTo(0.85f, tween(100, easing = LinearEasing))
scale.animateTo(
1f,
spring(dampingRatio = Spring.DampingRatioHighBouncy)
)
}
showHearts = true
repeat(7) { index ->
scope.launch {
delay(index * config.delayDuration)
heartList.add(index + Random.nextInt(1000))
}
}
},
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = null,
tint = Color.White,
modifier = Modifier.fillMaxSize()
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = if (showHearts) "Thank you!" else "Press L for love",
color = Color.White.copy(alpha = 0.7f),
fontSize = 20.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(32.dp))
Slider(
value = sliderValue,
onValueChange = { sliderValue = it },
valueRange = 0f..1f,
colors = SliderDefaults.colors(
thumbColor = Color(BlackL),
activeTrackColor = Color(BlackL)
),
modifier = Modifier.padding(horizontal = 32.dp)
)
}
}
@Composable
fun FloatingHeart(
key: Int,
config: HeartConfig,
onAnimationEnd: () -> Unit
) {
val angle = remember { Random.nextDouble(-90.0, 180.0) }
val baseRadius = remember { Random.nextDouble(100.0, 500.0).toFloat() }
val radius = baseRadius * config.radiusMultiplier
val xOffset = remember { Animatable(0f) }
val yOffset = remember { Animatable(0f) }
val alpha = remember { Animatable(0.5f) }
LaunchedEffect(key1 = key) {
launch {
xOffset.animateTo(
targetValue = (radius * cos(Math.toRadians(angle))).toFloat(),
animationSpec = tween(durationMillis = 1400, easing = LinearEasing)
)
}
launch {
yOffset.animateTo(
targetValue = (radius * -sin(Math.toRadians(angle))).toFloat() - 250f,
animationSpec = tween(durationMillis = 1400, easing = LinearEasing)
)
}
launch {
alpha.animateTo(
targetValue = 0f,
animationSpec = tween(durationMillis = 1400)
)
// onAnimationEnd() 🐛there is a break when the animation is terminated.
}
}
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = null,
tint = Color.White.copy(alpha = alpha.value),
modifier = Modifier
.offset {
IntOffset(xOffset.value.roundToInt(), yOffset.value.roundToInt() - 40)
}
.size(32.dp)
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment