Animating the Seasons: Building a Particle System with Android Canvas

One of the most satisfying things to code is a particle system. Snowflakes, falling leaves, fireflies — simple physics, beautiful results. Here’s how Seasons Live Wallpaper handles its snow, and how you can build something similar.

The Particle Data Class

data class Snowflake(
    var x: Float,
    var y: Float,
    val radius: Float,   // visual size in px
    val speedY: Float,   // fall speed (px/frame)
    val speedX: Float,   // horizontal drift (px/frame)
    val alpha: Int,      // 100–255 for a depth illusion
)

Spawning Particles

private fun spawnSnowflake(screenWidth: Int): Snowflake {
    val r = Random
    return Snowflake(
        x      = r.nextFloat() * screenWidth,
        y      = -20f,                          // start just above screen
        radius = r.nextFloat() * 6f + 2f,       // 2–8 px
        speedY = r.nextFloat() * 3f + 1f,       // 1–4 px/frame
        speedX = (r.nextFloat() - 0.5f) * 1.5f, // gentle drift
        alpha  = r.nextInt(155) + 100,           // 100–255
    )
}

The Update Loop

private val flakes = mutableListOf()
private const val MAX_FLAKES = 120

fun updateParticles(screenWidth: Int, screenHeight: Int) {
    while (flakes.size < MAX_FLAKES) flakes.add(spawnSnowflake(screenWidth))

    val iter = flakes.iterator()
    while (iter.hasNext()) {
        val f = iter.next()
        f.x += f.speedX + sin(f.y / 40.0).toFloat() * 0.5f  // sine sway
        f.y += f.speedY
        if (f.y > screenHeight + f.radius) iter.remove()
    }
}

Rendering

private val snowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.WHITE
    style = Paint.Style.FILL
}

fun drawParticles(canvas: Canvas) {
    for (flake in flakes) {
        snowPaint.alpha = flake.alpha
        canvas.drawCircle(flake.x, flake.y, flake.radius, snowPaint)
    }
}

Putting It Together

private fun drawFrame() {
    if (!surfaceHolder.surface.isValid) return
    val canvas = surfaceHolder.lockCanvas() ?: return
    try {
        drawBackground(canvas)           // sky, ground, trees
        updateParticles(width, height)
        drawParticles(canvas)
        drawUserStrokes(canvas)          // always on top
    } finally {
        surfaceHolder.unlockCanvasAndPost(canvas)
    }
}

With 120 snowflakes and sine sway you get a convincing winter scene at ~30 fps on any mid-range device. Swap circles for a Path leaf shape and tweak physics for an autumn variant.

👉 See the result live — download Seasons Live Wallpaper free

Scroll to Top