上篇文章我们完成了一条信息的测量和绘制,本篇我们来实现消息的平移动画
效果图如下:
在自定义View中,通常我比较喜欢额外创建一个Bitmap和一个Canvas来绘制动画效果。大家可以根据自己喜好修改,实现的方式有很多。
首先在首次测量的时候我们创建Canvas、Matrix、Bitmap,如果你的实际使用场景中,View的大小可能会更改,这里也可以每次测量都重新创建。
首先声明3个变量:
private lateinit var mBufferBitmap: Bitmap
private lateinit var mBufferCanvas: Canvas
private lateinit var mBufferMatrix: Matrix
在onMeasure
中创建:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 测量代码...
if (!this::mBufferBitmap.isInitialized) {
mBufferBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
mBufferCanvas = Canvas(mBufferBitmap)
mBufferMatrix = Matrix()
}
}
接下来我们修改一下消息数据模型,将每条消息的动画进度以及其他一些属性存储起来,以便后续拓展和修改。
data class Message(
val avatar: String,// 头像地址
val nickname: String,// 昵称
val joinRoom: Int,// 1=加入,否则均为退出,改为Boolean也可
var info: NicknameInfo,// 存储本条消息的宽高等信息
var shader: BitmapShader? = null,// 图片加载相关
var bitmap: Bitmap? = null,
var life: Int = 5,// 消息存活时间
val timing: Long = System.currentTimeMillis(),// 已消耗时间
var xProgress: Float = 1f,// x轴平移比例,取值1.0f~0.0f
var yProgress: Float = 0f,// y轴平移比例,同上
)
data class NicknameInfo(
val nickname: String,// 修改后的nickname(超过5个字符,后面变为省略号
val nicknameWidth: Float,// 昵称宽度
val messageWidth: Float,// 消息总宽
val statusTextWidth: Float// "加入房间"、"退出房间",这几个字的宽度。根据实际需求这里也可以改为全局变量,因为字宽基本上可以说是固定的
)
数据的命名和定义可以完全按照自己的喜好来,存储需要存储的数据即可。无论是自定义View还是其他,最终总是要落实到数据上的,定义好存储数据的结构和算法即可。
根据我们上面定义的数据,我们用mBufferCanvas和mBufferMatrix进行一个渲的染:p
private fun drawMessage() {
mBufferMatrix.reset()// 绘制前reset一下matrix,清空一下buffer bitmap。
// 这里使用 mBufferBitmap.eraseColor(Color.TRANSPARENT)也是可以的,至于两者有什么不同,我也不清楚(; ̄ェ ̄),欢迎留言补充
mBufferCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
val msg = messageList[0]// 上一篇文章中,我们使用的是一个全局变量,这里我偷偷换成了List,用其他形式也可以,因为需求中是显示两条消息,用数组也行,两个全局变量也可以,根据自己喜好即可
val info = msg.info
val xOffset = msg.xProgress * info.messageWidth
val yOffset = msg.yProgress * messageHeight + msg.yProgress * messagePadding
mBufferMatrix.setTranslate(xOffset, yOffset)// 平移效果通过matrix来实现,也可以直接在绘制的时候加上偏移量,我本来是这么做的,但创建的mBufferMatrix就显得很多余,我就改成用matrix了(´・ω・`)
mBufferCanvas.setMatrix(mBufferMatrix)// 记得设置上
drawMsg(// 绘制消息的代码我丢在一个方法里了,这样我绘制两条再调用一次drawMsg就行了,我可不想复制两遍代码(´・_・`)
msg,
info.messageWidth,
info.nicknameWidth,
info.nickname
)
post { invalidate() }// 防止子线程调用,进行一个post
}
/**
* DO NOT CALL THIS FUNCTION
*/
private fun drawMsg(
msg: Message,
messageWidth: Float,
nicknameWidth: Float,
nickname: String
) {
path.reset()// 用前先reset,好习惯
paint.color = Color.parseColor("#F3F3F3")
val statusText = if (msg.joinRoom == 1) "进入直播间" else "退出直播间"//状态直接传进来也可,个人喜好
// 接下来你看到的所有xOffset、yOffset,都是我改用matrix之前的逻辑,把0f删掉、把x/yOffset取消注释,就变成不用matrix的版本了(´・ω・`)
val messageLeft = measuredWidth - messageWidth// + xOffset
path.addArc(// 给path添加一个半圆
messageLeft,
//yOffset,
0f,
messageLeft + avatarPadding + avatarHeight.toFloat(),
/*yOffset*/0f + messageHeight.toFloat(),
90f,
180f
)
// 给path添加矩形
path.moveTo(messageLeft + avatarHeight.shr(1).toFloat(), /*yOffset*/0f)
path.lineTo(measuredWidth.toFloat(), /*yOffset*/0f)
path.lineTo(measuredWidth.toFloat(), /*yOffset*/0f + messageHeight.toFloat())
path.lineTo(
messageLeft + avatarHeight.shr(1).toFloat(), /*yOffset*/
0f + messageHeight.toFloat()
)
paint.color = Color.parseColor("#434343")// 背景色
mBufferCanvas.drawPath(path, paint)// 填充
// 绘制文本
paint.color = Color.WHITE
mBufferCanvas.drawText(
statusText,
messageLeft + avatarHeight + avatarPadding.shl(1) + nicknameWidth + messagePadding,
//(measuredWidth - statusTextWidth - statusTextPadding) + /*xOffset*/上面提到放开offset的算法,这里的注视就没删除,留下做参考0f,
messageHeight.shr(1) + fontCenterOffset + /*yOffset*/0f,
paint
)
paint.color = Color.parseColor("#BCBCBC")// 绘制昵称
mBufferCanvas.drawText(
nickname,
messageLeft + avatarPadding.shl(1) + avatarHeight,
//(messageWidth - statusTextWidth - statusTextPadding.shl(1) - nicknameWidth) + /*xOffset*/0f,
messageHeight.shr(1) + fontCenterOffset + /*yOffset*/0f,
paint
)
msg.bitmap?.let {// 图片加载完成的话,绘制头像
mBufferCanvas.save()
paint.shader = msg.shader
val translateOffset = (messageHeight - it.width).shr(1)
mBufferCanvas.translate(
messageLeft + translateOffset,
/*yOffset*/0f + translateOffset.toFloat()
)
mBufferCanvas.drawCircle(
it.width.shr(1).toFloat(),
it.width.shr(1).toFloat()/*messageHeight.shr(1).toFloat()*/,
avatarHeight.shr(1).toFloat(),
paint
)
paint.shader = null
mBufferCanvas.restore()
}
}
完成上面的代码后只需要修改x轴和y轴的变量,即可实现"动画"了。动手试试吧(´・ω・`)
下篇文章我们来实现添加消息、计时、生命结束后删除消息等功能,还有真正的动画效。