Skip to content

Instantly share code, notes, and snippets.

@yuriyskulskiy
Last active September 30, 2024 03:18
Show Gist options
  • Save yuriyskulskiy/504dd4779f9a909357bb5e1d7911157b to your computer and use it in GitHub Desktop.
Save yuriyskulskiy/504dd4779f9a909357bb5e1d7911157b to your computer and use it in GitHub Desktop.
@RequiresApi(Build.VERSION_CODES.S)
val metaBallRenderEffect =
RenderEffect.createChainEffect(
RenderEffect.createColorFilterEffect(
ColorMatrixColorFilter(
ColorMatrix(
floatArrayOf(
1f, 0f, 0f, 0f, 0f,
0f, 1f, 0f, 0f, 0f,
0f, 0f, 1f, 0f, 0f,
0f, 0f, 0f, 160f, -10000f
)
)
)
),
RenderEffect.createBlurEffect(100f, 100f, Shader.TileMode.MIRROR)
).asComposeRenderEffect()
@Composable
fun MovingCirclesWithMetaballEffect(circleCount: Int = 15) {
var boxSize by remember { mutableStateOf(IntSize(0, 0)) }
var circles by remember { mutableStateOf(listOf<CircleModel>()) }
val density = LocalDensity.current
val lifecycleOwner = LocalLifecycleOwner.current
var isAnimationRunning by remember { mutableStateOf(true) }
// Observe lifecycle to control animation only when "screen is resumed"
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
isAnimationRunning = when (event) {
Lifecycle.Event.ON_RESUME -> true
Lifecycle.Event.ON_PAUSE -> false
else -> isAnimationRunning
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
LaunchedEffect(boxSize) {
if (boxSize.width > 0 && boxSize.height > 0) {
circles = generateRandomCircles(circleCount, boxSize.width, boxSize.height, density)
}
}
LaunchedEffect(circles, isAnimationRunning) {
while (isAnimationRunning) {
awaitFrame()
circles = circles.map { circle ->
val radiusPx = with(density) { circle.size.toPx() / 2 }
val updatedX = circle.x + circle.velocityX
val updatedY = circle.y + circle.velocityY
val newVelocityX =
if (updatedX - radiusPx < 0 || updatedX + radiusPx > boxSize.width) {
-circle.velocityX
} else {
circle.velocityX
}
val newVelocityY =
if (updatedY - radiusPx < 0 || updatedY + radiusPx > boxSize.height) {
-circle.velocityY
} else {
circle.velocityY
}
circle.copy(
x = updatedX,
y = updatedY,
velocityX = newVelocityX,
velocityY = newVelocityY
)
}
}
}
val renderEffect = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
metaBallRenderEffect
} else {
throw UnsupportedOperationException("Unsupported Android version.")
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.onGloballyPositioned { coordinates -> boxSize = coordinates.size }
) {
Canvas(
modifier =
Modifier
.fillMaxSize()
.graphicsLayer {
this.renderEffect = renderEffect
}
) {
circles.forEach { circle ->
val radiusPx = with(density) { circle.size.toPx() / 2 }
// Draw the circles
drawCircle(
color = Color.White,
radius = radiusPx,
center = Offset(circle.x, circle.y)
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment