博客原文:[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()
}
複製代碼
分析一下效果圖,就是在隨機位置加了不少點,而後這些點以隨機速度往隨機方向作勻速直線運動。 那咱們全部須要的要素都在這裏了:優化
那我就定義一個類保存這些要素就好,動畫
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()
}
複製代碼
這樣就能讓星星在夜空中動起來了。
不過,咱們想一想,星空中不僅有星星。還有月亮、太陽和超人。咱們能夠優化優化讓它變得更通用。
咱們回去看看星星模型:
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
的同步問題,物體移動出範圍後如何處理的問題。 當這些問題你都處理好了,美麗的星空就從你的手中誕生了。