android空间动态背景,[Android] 做了一个星空背景的动态 Drawable
新项目需要做一个星空背景,顺便说下怎么做一个动态 Drawable
先看最终效果图:
我们的目标是一个叫 StarrySky 的 动态Drawable, 用法像这样:
imageView.setImageDrawable(starrySky)
// or
imageView.background = starrySky
starrySky.start()
复制代码
所以基础结构就是
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方法来计算。
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异常,加上粗暴的同步锁
val stars = HashSet()
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 {
synchronized(LOCK) {
val set = HashSet()
set.addAll(stars)
return set
}
}
复制代码
画出来:
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()
// 开始动画
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 的同步问题,物体移动出范围后如何处理的问题。
当这些问题你都处理好了,美丽的星空就从你的手中诞生了。
发布评论