SUBSCRIBE NOW
I always learn something just by skimming it that makes me want to bookmark the issue now and dig deeper later
SUBSCRIBE NOW
Keep up the good work with the newsletter 💪 I really enjoy it
SUBSCRIBE NOW
Dispatch is a must read for Android devs today and my go-to for keeping up with all things Jetpack Compose
SUBSCRIBE NOW
Dispatch has been my go-to resource as it's packed with useful information while being fun at the same time
SUBSCRIBE NOW
The content is light, fun, and still useful. I especially appreciate the small tips that are in each issue
SUBSCRIBE NOW
I truly love this newsletter ❤️🔥 Spot on content and I know there's a lot of effort that goes behind it
SUBSCRIBE NOW
Thanks for taking the time and energy to do it so well
JetpackCompose.app's Newsletter
I always learn something just by skimming it that makes me want to bookmark the issue now and dig deeper later
JetpackCompose.app's Newsletter
Keep up the good work with the newsletter 💪 I really enjoy it
JetpackCompose.app's Newsletter
Dispatch is a must read for Android devs today and my go-to for keeping up with all things Jetpack Compose
JetpackCompose.app's Newsletter
Dispatch has been my go-to resource as it's packed with useful information while being fun at the same time
JetpackCompose.app's Newsletter
The content is light, fun, and still useful. I especially appreciate the small tips that are in each issue
JetpackCompose.app's Newsletter
I truly love this newsletter ❤️🔥 Spot on content and I know there's a lot of effort that goes behind it
JetpackCompose.app's Newsletter
Thanks for taking the time and energy to do it so well
Snowfall Modifier
Author: Sebastiano Poggi
A Modifier that adds the effect of snowflakes falling on the composable that it's applied to.
internal fun Modifier.snowfall() = composed { | |
var snowflakesState by remember { | |
mutableStateOf(SnowflakesState(-1, IntSize(0, 0))) | |
} | |
LaunchedEffect(Unit) { | |
while (isActive) { | |
withFrameNanos { newTick -> | |
val elapsedMillis = | |
(newTick - snowflakesState.tickNanos).nanoseconds.inWholeMilliseconds | |
val wasFirstRun = snowflakesState.tickNanos < 0 | |
snowflakesState.tickNanos = newTick | |
if (wasFirstRun) return@withFrameNanos | |
for (snowflake in snowflakesState.snowflakes) { | |
snowflake.update(elapsedMillis) | |
} | |
} | |
} | |
} | |
onSizeChanged { newSize -> snowflakesState = snowflakesState.resize(newSize) } | |
.clipToBounds() | |
.drawWithContent { | |
drawContent() | |
snowflakesState.draw(drawContext.canvas) | |
} | |
} | |
fun ClosedRange<Float>.random() = | |
ThreadLocalRandom.current().nextFloat() * (endInclusive - start) + start | |
fun Float.random() = | |
ThreadLocalRandom.current().nextFloat() * this | |
fun Int.random() = | |
ThreadLocalRandom.current().nextInt(this) | |
fun IntSize.randomPosition() = | |
Offset(width.random().toFloat(), height.random().toFloat()) | |
private const val snowflakeDensity = 0.1 | |
private val incrementRange = 0.4f..0.8f | |
private val sizeRange = 5.0f..12.0f | |
private const val angleSeed = 25.0f | |
private val angleSeedRange = -angleSeed..angleSeed | |
private const val angleRange = 0.1f | |
private const val angleDivisor = 10000.0f | |
internal data class SnowflakesState( | |
var tickNanos: Long, | |
val snowflakes: List<Snowflake>, | |
) { | |
constructor(tick: Long, canvasSize: IntSize) : this(tick, createSnowflakes(canvasSize)) | |
fun draw(canvas: Canvas) { | |
snowflakes.forEach { it.draw(canvas) } | |
} | |
fun resize(newSize: IntSize) = copy(snowflakes = createSnowflakes(newSize)) | |
companion object { | |
private fun createSnowflakes(canvasSize: IntSize): List<Snowflake> { | |
val canvasArea = canvasSize.width * canvasSize.height | |
val normalizedDensity = snowflakeDensity.coerceIn(0.0..1.0) / 500.0 | |
val snowflakesCount = (canvasArea * normalizedDensity).roundToInt() | |
return List(snowflakesCount) { | |
Snowflake( | |
incrementFactor = incrementRange.random(), | |
size = sizeRange.random(), | |
canvasSize = canvasSize, | |
position = canvasSize.randomPosition(), | |
angle = angleSeed.random() / angleSeed * angleRange + (PI / 2.0) - (angleRange / 2.0) | |
) | |
} | |
} | |
} | |
} | |
private val snowflakePaint = Paint().apply { | |
isAntiAlias = true | |
color = Color.White | |
style = PaintingStyle.Fill | |
} | |
internal class Snowflake( | |
private val incrementFactor: Float, | |
private val size: Float, | |
private val canvasSize: IntSize, | |
position: Offset, | |
angle: Double | |
) { | |
private var position by mutableStateOf(position) | |
private var angle by mutableStateOf(angle) | |
fun update(elapsedMillis: Long) { | |
val increment = incrementFactor * (elapsedMillis / baseFrameDurationMillis) * baseSpeedPxAt60Fps | |
val xDelta = (increment * cos(angle)).toFloat() | |
val yDelta = (increment * sin(angle)).toFloat() | |
position = Offset(position.x + xDelta, position.y + yDelta) | |
angle += angleSeedRange.random() / angleDivisor | |
if (position.y > canvasSize.height + size) { | |
position = Offset(position.x, -size) | |
} | |
} | |
fun draw(canvas: Canvas) { | |
canvas.drawCircle(position, size, snowflakePaint) | |
} | |
} |
Have a project you'd like to submit? Fill this form, will ya!
If you like this snippet, you might also like:
Maker OS is an all-in-one productivity system for developers
I built Maker OS to track, manage & organize my life. Now you can do it too!