[Android] 作了一个星空背景的动态 Drawable - StarrySky

博客原文:[Android] 作了一个星空背景的动态 Drawable - StarrySkyandroid

新项目须要作一个星空背景,顺便说下怎么作一个动态 Drawablecanvas

先看最终效果图:bash


咱们的目标是一个叫 StarrySky动态Drawable, 用法像这样:less

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

starrySky.start()
复制代码

因此基础结构就是ide

class StarrySky: Drawable(), Animatable {
    /// xxx
    override fun draw(canvas: Canvas)
    override fun start()
    override fun stop()
    override fun isRunning()
}
复制代码

分析一下效果图,就是在随机位置加了不少点,而后这些点以随机速度随机方向作匀速直线运动。 那咱们全部须要的要素都在这里了:优化

  1. 随机位置
  2. 随机速度
  3. 随机方向

那我就定义一个类保存这些要素就好,动画

class Star(
    var x: Float,
    var y: Float,
    var speed: Int, // pixels per second
    var direction: Int  // degree (0-360)
)
复制代码

由于是星星都动态的,因此要能够计算下一帧的位置,加一个move方法来计算。ui

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())
    }
}
复制代码

而后给 StarrySky 加一个列表来保存这些星星, 为了不concurrent异常,加上粗暴的同步锁spa

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
    }
}
复制代码

画出来:3d

fun draw(canvas: Canvas) {
    canvas.drawColor(backgroundColor)
    val currentStars = copyStar()
    for (star in currentStars) {
        canvas.drawCircle(star.x, star.y, 2f, starPaint)
    }
}
复制代码

怎么让他们动起来呢? 方法不少,Timer ValueAnimator 甚至手动delay均可以。咱们的目标就是每过 16ms(每秒60帧) 能更新一下咱们的位置。而后告诉 drawable,我位置更新了,你能够从新画一遍了。

我这里用了Timer

fun start() {
    /// xxx
    timer.schedule(object : TimerTask() {
        override fun run() {
            val currentTime = System.currentTimeMillis()
            update((currentTime - lastTime).toInt())
            lastTime = currentTime
        }
    }, 0, 16)
}
fun update(delta: Int) {
    // xxx
    // 这里要注意处理同步问题, 我就简写
    for star in stars:
        star.move(delta)
}
复制代码

ok,新位置计算结束 告诉 drawable 从新绘制:

fun update(delta: Int) {
    // 计算新位置后
    invalidateSelf()
}
复制代码

这样就能让星星在夜空中动起来了。

不过,咱们想一想,星空中不仅有星星。还有月亮、太阳和超人。咱们能够优化优化让它变得更通用。

作成通用型 Drawable

咱们回去看看星星模型

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())
    }
}
复制代码

咱们先给他从新命个名,叫Model

月亮、太阳、超人等等,这每类模型和星星同样的点在于,他们必然都有坐标,可是移动模式可能不同。 因此咱们能够把速度和方向提出来作抽象获得:

abstract class Model(
    var position: Point
) {
    abstract fun move(delta: Int)
}
复制代码

星星咱们知道怎么画,就画个小圆就好了。但是若是你想要画月亮画太阳,或者画个大星星,那确定就不能仍是那样了。 因此绘制的部分也要抽象出来。

abstract class Model(
    var position: Point
) {
    abstract fun move(delta: Int)
    abstract fun draw(canvas: Canvas)
}
复制代码

星星就变成了

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)
    }
}
复制代码

如今整个星空StarrySky看起来像这样了:

class StarrySky: Drawable(), Animatable {
    /// xxx
    val models = HashSet<Model>()

    // 开始动画
    override fun start() {
        // 每 16s 更新
        timer.schedule(object : TimerTask() {
            override fun run() {
                val currentTime = System.currentTimeMillis()
                update((currentTime - lastTime).toInt())
                lastTime = currentTime
                // 重绘
                invalidateSelf()
            }
        }, 0, 16)
    }
    override fun stop()
    override fun isRunning()
    
    // 计算新位置
    fun update(delta: Int) {
        models.forEach {
            it.move(delta)
        }
    }
    
    // 绘制模型
    override fun draw(canvas: Canvas) {
        models.forEach {
            it.draw(canvas)
        }
    }
}
复制代码

固然,这里都是伪代码。你实际写代码还要注意更多的细节,好比 Set 的同步问题,物体移动出范围后如何处理的问题。 当这些问题你都处理好了,美丽的星空就从你的手中诞生了。

相关文章
相关标签/搜索