Skip to content

Instantly share code, notes, and snippets.

@ardakazanci
Created January 29, 2024 17:26
Show Gist options
  • Select an option

  • Save ardakazanci/c32923d9f8a9d06c181e10a33c233003 to your computer and use it in GitHub Desktop.

Select an option

Save ardakazanci/c32923d9f8a9d06c181e10a33c233003 to your computer and use it in GitHub Desktop.
Spring Animation for Jetpack Compose
class DotParticle(
var position: Offset,
var velocity: Offset,
val color: Color,
var radius: Float = 5f
) {
fun move() {
position += velocity
}
}
class Dot {
var position = Animatable(Offset.Zero, Offset.VectorConverter)
val color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat(), 1f)
var target = Offset.Zero
val trail = mutableListOf<Offset>()
suspend fun animateToTarget() {
coroutineScope {
launch {
position.animateTo(target, animationSpec = TweenSpec(durationMillis = 3000)) {
trail.add(value)
if (trail.size > 20) {
trail.removeFirst()
}
}
}
}
}
fun setRandomTarget(canvasSize: Offset) {
target = Offset(
x = Random.nextFloat() * canvasSize.x,
y = Random.nextFloat() * canvasSize.y
)
}
}
@Composable
fun SpringDrawingShape() {
var canvasSize by remember { mutableStateOf(Offset.Zero) }
val dots = remember { List(10) { Dot() } }
val coroutineScope = rememberCoroutineScope()
var particles by remember { mutableStateOf(listOf<DotParticle>()) }
var tapCount by remember { mutableStateOf(0) }
Canvas(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { canvasSize = Offset(it.width.toFloat(), it.height.toFloat()) }
.pointerInput(Unit) {
detectTapGestures {
tapCount++
dots.forEach { dot ->
dot.setRandomTarget(canvasSize)
coroutineScope.launch {
dot.animateToTarget()
}
}
}
}
) {
particles.forEach { particle ->
drawCircle(color = particle.color, radius = particle.radius, center = particle.position)
}
dots.forEach { dot ->
if (dot.trail.size > 1) {
val path = Path().apply {
var firstPoint = true
dot.trail.forEach { point ->
if (firstPoint) {
moveTo(point.x, point.y)
firstPoint = false
} else {
lineTo(point.x, point.y)
}
}
}
drawPath(path, color = dot.color, alpha = 0.8f, Stroke(width = 3.dp.toPx()))
}
drawCircle(color = dot.color, radius = 20f, center = dot.position.value)
}
}
LaunchedEffect(Unit) {
coroutineScope.launch {
while (isActive) {
for (i in dots.indices) {
for (j in i + 1 until dots.size) {
val dot1 = dots[i]
val dot2 = dots[j]
val distance = sqrt(
(dot1.position.value.x - dot2.position.value.x).pow(2) +
(dot1.position.value.y - dot2.position.value.y).pow(2)
)
if (distance < 40f) {
val newParticle = mutableListOf<DotParticle>()
repeat(20) {
val angle = Random.nextDouble(0.0, 2 * Math.PI)
val speed = Random.nextDouble(2.0, 5.0)
newParticle += DotParticle(
position = dot1.position.value,
velocity = Offset(cos(angle).toFloat() * speed.toFloat(), sin(angle).toFloat() * speed.toFloat()),
color = dot1.color
)
}
particles = newParticle
}
}
}
particles.forEach { it.move() }
delay(16L)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment