古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。这篇文章主要讲述Android技术分享| 自习室自定义View代替通知动画(完)相关的知识,希望能为你提供帮助。
在之前的文章中我们实现了自定义View需要的基本功能,本篇中我们通过 Timer 实现动画功能。我偷偷修改了一些数据结构,一会在下面贴出来。
最终效果图:
文章图片
动画是通过 Timer 每17毫秒调用 View#post 来调用主线程更新一帧。定义一个 interpolator 使动画效果更自然(逐渐减速的效果)。
【Android技术分享| 自习室自定义View代替通知动画(完)】首先定义一个存储执行动画相关的数据结构:
private data class AnimInfo(
val block: (percentage: Float) ->
Unit,// 每帧调用
val duration: Long = 510,
val progress: Long = 0L,
val done: () ->
Unit = // 动画结束时调用
)
还有修改过的存储消息相关的数据结构:
data class Message(
val avatar: String,// 头像
val nickname: String,// 昵称
val joinRoom: Int = 1,// 1=加入,其他为退出
var shader: BitmapShader? = null,
var bitmap: Bitmap? = null,
var life: Long = 0L,// 当前时间
val lifeTime: Long = 5000L, // 最大存在时间
)
使用链表来存储 Message 和 AnimInfo 数据:
private val animArr = LinkedList<
AnimInfo>
()
private val dataArr = LinkedList<
Message>
()
使用一个 Timer 计时动画及更新 Message 的已存在时间。在 init 方法中初始化:
init
paint.textSize = fontSize.toFloat()
paint.style = Paint.Style.FILL
val metrics = paint.fontMetrics
fontCenterOffset = (abs(metrics.top) - metrics.bottom) / 2ftimer = Timer()
timer.schedule(object : TimerTask()
override fun run()
if (dataArr.isNotEmpty()) // 存在时间计时
dataArr.forEach
it.life += 17Lval first = dataArr.first
if (first.life >
= first.lifeTime) // 当第一条超过最高存在时间时移除
dismissFirstMessage(true)if (animArr.isEmpty()) // 未注册任何动画则直接跳过
returnval i = animArr.iterator()// 序列化移除较为方便
while (i.hasNext())
val next = i.next()
next.progress += 17Lvar percentage = next.progress.toFloat() / next.duration
if (percentage >
1.0f)
percentage = 1.0f
postnext.block.invoke(interpolator(percentage)) // 每帧调用if (next.progress >
= next.duration) // 动画执行完毕则调用 done 并移除自己
postnext.done.invoke()
i.remove(), 0, 17)
interpolator 的实现:
private fun interpolator(x: Float): Float = (1.0f - (1.0f - x) * (1.0f - x))
记得在 onDetachedFromWindow 中将 timer 任务注销:
override fun onDetachedFromWindow()
timer.cancel()
timer.purge()
super.onDetachedFromWindow()
定义 registerAnimator 方法,使开启一个动画更有仪式感(不是
private fun registerAnimator(animInfo: AnimInfo)
animArr.add(animInfo)
删除了 drawMessage 方法,添加了 addMessage 方法和 removeFirstMessage 方法。
addMessage 方法:
fun addMessage(msg: Message)
if (!this::mBufferBitmap.isInitialized) // 尚未初始化完成通过 post 等待初始化
postaddMessage(msg)
return// 动画执行中或目前展示的通知数量已达上限则添加到 waitList 中,等待执行
if (animRunning || dataArr.size == limitMessageSize)
if (dataArr.size == limitMessageSize)
dismissFirstMessage()
waitList.add(msg)
returnanimRunning = true
dataArr.add(msg)val nicknameWidth = paint.measureText(msg.nickname)
val msgWidth = nicknameWidth + basedMessageWidthloadImage(msg.avatar)bitmap, b ->
if (!b) return@loadImageval shader = BitmapShader(bitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
msg.let
it.bitmap = bitmap
it.shader = shader// 这里只更新新增的那一条,所以不需要清空之前绘制好的数据
val yOffset = (dataArr.size - 1) * (messageHeight + messagePadding).toFloat()
registerAnimator(AnimInfo( percentage ->
val xOffset = msgWidth + -(percentage * msgWidth)
mBufferMatrix.reset()
mBufferMatrix.postTranslate(xOffset, yOffset)
mBufferCanvas.setMatrix(mBufferMatrix)
drawMsg(msg, msgWidth, nicknameWidth)
invalidate()
)
// 动画结束后先判断是否有等待删除的任务,再判断是否有等待添加的任务
animRunning = false
if (waitingRemove >
0)
removeFirstMessage()
else if (waitList.isNotEmpty())
addMessage(waitList.removeFirst()))
removeFirstMessage 方法:
// timer 每17毫秒轮询一次,如果动画在执行中会导致 waitingRemove 增加非常多
// 所以 timer 传进来的不增加等待删除次数
fun removeFirstMessage(isFromTimer: Boolean = false)
if (dataArr.isEmpty())
returnif (animRunning)
if (!isFromTimer) waitingRemove++
returnanimRunning = true
registerAnimator(AnimInfo( percentage ->
// 因为改动两条数据并且是上下平移,需要清空上次绘制内容
mBufferBitmap.eraseColor(Color.TRANSPARENT)
for (i in 0 until dataArr.size)
val item = dataArr[i]
val nicknameWidth = paint.measureText(item.nickname)
val msgWidth = nicknameWidth + basedMessageWidth
val msgHeight = (messageHeight + messagePadding).toFloat()
mBufferMatrix.reset()
mBufferMatrix.setTranslate(0f, (i * msgHeight) - (percentage * msgHeight))
mBufferCanvas.setMatrix(mBufferMatrix)
drawMsg(item, msgWidth, nicknameWidth)
invalidate())
animRunning = false
dataArr.removeFirst().bitmap?.recycle()
if (waitList.size >
0) // 先判断是否有等待添加的消息,与 addMessage 刚好相反
addMessage(waitList.removeFirst())
else if (waitingRemove >
0)
if (dataArr.isNotEmpty())
waitingRemove--
removeFirstMessage()
return@AnimInfowaitingRemove = 0)
还有一些小问题没有处理好,比如短时间内连续调用 addMessage 会导致等待删除的任务过多(虽然已经做了兜底处理),比如图片加载没有做中断处理。
各位如果想要上到业务环境还需将这些问题完善。
源码地址:点击这里
文章图片
推荐阅读
- powershell将计算机加入到AD域安全组,并输出日志
- 网络不可用怎么办(无法访问互联网怎么办?网络故障原因大起底)
- #私藏项目实操分享#云开发数据库有哪些常用操作
- 百度搜索中台海量数据管理的云原生和智能化实践
- #yyds干货盘点#linux命令--rm,rmdir,mv
- #yyds干货盘点#MySQL索引优化系列(索引失效)
- #yyds干货盘点#SpringSecurity默认页面生成
- KubeCon 2021|使用 eBPF 代替 iptables 优化服务网格数据面性能
- mybatis缓存机制详解#yyds干货盘点#