Drawing Animated Stars on Canvas

Written with Kotlin for Android

Developed for Wakey

Wakey is a simple & beautiful animated alarm clock, featuring a spectacular design and an enjoyable experience — guaranteed to wake you up with a smile everyday!

With our smiling sunrise, and grumpy lunar animations, this is the most unique alarm clock the universe. Or at least in our solar system!

With a bloat-free approach, Wakey is a dedicated alarm clock app, with no added features (timer, stopwatch, etc)

Wakey’s different alarm states UI (snooze, default and dismiss — from left to right)

Looking to the Stars

In the first few alpha versions of Wakey, the window background had some static stars on it. It was great at the beginning, it felt very colorful for a while. Slowly though, I got used to the static image of the tiny stars, and I longed for the stars to really pop! I wanted the sky to come alive, and naturally I decided that the best way to achieve this — would be to write some code!

I was gonna build a view that would randomly generate some stars on the canvas. In this article we’ll cover the general steps of how I’ve created this. You can access the full source code on GitHub Gist. You can also check out Wakey on your device and see how it runs via Google Play, or in the video below.


Coding Time

So I’ve started with the attribute parameters I knew the view would need to initiate itself. I wanted to decide how many stars I want for my view, what colors the stars would be. What would be the minimum and maximum star sizes, and what is the size above which a star will be considered a big star.

class AnimatedStarsView
@kotlin.jvm.JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {

private var starCount: Int
private var starColors: IntArray
private var bigStarThreshold: Int
private var minStarSize: Int
private var maxStarSize: Int
}

We will initiate the actual stars, only after we know the view’s size, and every time the view’s size changes.

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h


if (viewWidth > 0 && viewHeight > 0) {
// init stars every time the size of the view has changed
initStars()
}
}
/**
* create x stars with a random point location and opacity
*/
private fun initStars() {
stars = List(starCount) { 
// star constructor
Star(
starConstraints,
Math.round(Math.random() * viewWidth).toInt(),
// x
Math.round(Math.random() * viewHeight).toInt(), //
y
Math.random(),
// opacity
starColors[it % starColors.size],
// color
viewWidth,
viewHeight,
// random color function
{ starColors[random.nextInt(starColors.size)] }
)
    // so we know lateinit var was initiated
initiated = true
}
}

Notice how we initialize the list with the List constructor — courtesy of Dominik Mičuta & Arek Olek, who pointed this out in the comments below.

It takes a function, that will iterate over the starCount, and will init a new Star object for every iteration. This is instead of initiating the list, then running a for loop to add each star “manually”.

We will also do something similar for generating a random color, for each new Star when passing the last Star parameter. Instead of implementing an interface, we will just pass a fun to be invoked.

The Star’s constructor will contain a val colorListener: () -> Int parameter, and so we could pass a function called colorListener, returning an Int as result. That Int is the color value for the newly positioned star, which would be pulled randomly from the starColor list.


The view needs a Timer and TimerTask for scheduling the canvas redraw function. This will happen 60 times per second, but the timer will start working only when the activity starts, and will stop when the activity stops.

// call these functions from the activity's onStart and onStop functions
fun onStart() {

timer = Timer()
task = timerTask {
invalidateStars()

}
    // set timer to run every 16 milliseconds (fps = 1000 / 60)
timer.scheduleAtFixedRate(task, 0, fps)

}

fun onStop() {

task.cancel()
timer.cancel()

}

We recalculate the stars position and alpha on a new thread, once per frame - and then request a canvas redraw. Because the calculation should be rather quick for a low amount of stars (say up to 100, maybe more), and will easily fill the screen, we can assume we will still get roughly 60 FPS on this animation.

private fun invalidateStars() {
Thread(Runnable {
        stars.forEach { it.calculateFrame(viewWidth, viewHeight) }
        starsCalculatedFlag = true
        postInvalidate()
    }).start()

}
override fun onDraw(canvas: Canvas?) {
var newCanvas = canvas
stars.forEach { star ->
newCanvas = star.draw(newCanvas)
}
super.onDraw(newCanvas)
}

Just the Beginning

These are only the basics of how we draw stars on a canvas on Android. I will not cover the Star class in this article, but you can see it right now along with the rest of the source code on GitHub Gist.

Update: The library is now uploaded to GitHub, and is available for implementation through Gradle.

If you want to see what the animated stars look like, be sure to download Wakey from the Play Store and give it a whirl, or check it out on YouTube.

In the future, I will cover more about how Wakey was born and released.

Good Night!

Moony doesn’t like that sun sneaking up on him in the morning