[Android] made a dynamic Drawable – StarrySky with a starry background

[making] (github.com/auv1107/hua…).

The new project needs to make a starry background. By the way, how to make a dynamic Drawable

Take a look at the final image first:


Our target is a dynamic Drawable called StarrySky, used like this:

imageView.setImageDrawable(starrySky)
// or
imageView.background = starrySky

starrySky.start()
Copy the code

So the infrastructure is

class StarrySky: Drawable(), Animatable {
    /// xxx
    override fun draw(canvas: Canvas)
    override fun start()
    override fun stop()
    override fun isRunning()
}
Copy the code

Analyze the effect diagram, it is in a random position to add a lot of points, and then these points at random speed in a random direction of uniform linear motion. So here we have all the ingredients we need:

  1. Random location
  2. A random speed
  3. Random direction

So I’m just going to define a class that holds these elements,

class Star(
    var x: Float,
    var y: Float,
    var speed: Int, // pixels per second
    var direction: Int  // degree (0-360)
)
Copy the code

Since the stars are dynamic, to calculate the position of the next frame, add a move method to calculate it.

class Star(
    var x: Float,
    var y: Float,
    var speed: Int, // pixels per second
    var direction: Int  // degree (0-360)
) {
    fun move(delta: Int) {
        x += speed * delta / 1000f * cos(direction.toFloat())
        y += speed * delta / 1000f * sin(direction.toFloat())
    }
}
Copy the code

StarrySky is then given a list to hold these stars, with a rude synchronization lock to avoid concurrent exceptions

val stars = HashSet<Star>()
private val LOCK = Any()
fun addStar(star: Star) {
    synchronized(LOCK) {
        stars.add(star)
    }
}
fun removeStar(star: Star) {
    synchronized(LOCK) {
        stars.remove(star)
    }
}
fun copyStar(): HashSet<Star> {
    synchronized(LOCK) {
        val set = HashSet<Star>()
        set.addAll(stars)
        return set
    }
}
Copy the code

Picture this:

fun draw(canvas: Canvas) {
    canvas.drawColor(backgroundColor)
    val currentStars = copyStar()
    for (star in currentStars) {
        canvas.drawCircle(star.x, star.y, 2f, starPaint)
    }
}
Copy the code

How do we get them to move? There are many methods, including Timer ValueAnimator and even manual delay. Our goal is to update our location every 16ms(60 frames per second). And then tell Drawable, I’ve updated my position, and you can redraw it.

I’m using Timer here

fun start() { /// xxx timer.schedule(object : TimerTask() { override fun run() { val currentTime = System.currentTimeMillis() update((currentTime - lastTime).toInt()) CurrentTime = currentTime}, 0, 16)} fun update(delta: Int) { star.move(delta) }Copy the code

Drawable is told to redraw when the new position is computed:

Fun update(delta: Int) {// invalidateSelf()}Copy the code

This will make the stars move in the night sky.

But let’s see, there are more than just stars in the sky. And the moon and the sun and Superman. We can optimize it to make it more versatile.

Make it a universal Drawable

Let’s go back to the star model:

class Star(
    var x: Float,
    var y: Float,
    var speed: Int, // pixels per second
    var direction: Int  // degree (0-360)
) {
    fun move(delta: Int) {
        x += speed * delta / 1000f * cos(direction.toFloat())
        y += speed * delta / 1000f * sin(direction.toFloat())
    }
}
Copy the code

Let’s rename him Model

The moon, the sun, superman, and so on, each of these models has the same point as the stars in that they must have coordinates, but the patterns of movement may be different. So we can abstract out the velocity and direction:

abstract class Model(
    var position: Point
) {
    abstract fun move(delta: Int)
}
Copy the code

We know how to draw a star, just a little circle. But if you want to draw the moon or the sun, or draw a big star, it can’t be the same. So the part that you draw is also abstracted.

abstract class Model(
    var position: Point
) {
    abstract fun move(delta: Int)
    abstract fun draw(canvas: Canvas)
}
Copy the code

The star becomes

class Star(position: Point, val speed: Int, val direction: Int, paint: Paint): Model(position) {
    fun move(delta: Int) {
        position.x += speed * delta / 1000f * cos(direction.toFloat())
        position.y += speed * delta / 1000f * sin(direction.toFloat())
    }
    fun draw(canvas: Canvas) {
        canvas.drawCircle(position.x, position.y, 2f, paint)
    }
}
Copy the code

Now the entire StarrySky looks like this:

class StarrySky: Drawable(), Animatable {/// XXX val models = HashSet<Model>() // start animation Override fun start() { TimerTask() { override fun run() { val currentTime = System.currentTimeMillis() update((currentTime - lastTime).toInt()) InvalidateSelf ()}}, 0, 16)} override fun stop() override fun isRunning() Int) {models. ForEach {it. Move (delta)}} Canvas) { models.forEach { it.draw(canvas) } } }Copy the code

Of course, this is all pseudo-code. You have to pay attention to more details when you actually write code, such as the synchronization of sets and how objects are handled when they move out of range. When you handle these problems well, a beautiful sky is born from your hands.