[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 的同步問題,物體移動出範圍後如何處理的問題。 當這些問題你都處理好了,美麗的星空就從你的手中誕生了。

相關文章
相關標籤/搜索