In the last article, we completed the measurement and drawing of a message. In this article, we will realize the translation animation of the message

The renderings are as follows:

In custom views, I usually prefer to create an extra Bitmap and a Canvas to draw animations. You can modify it as you like, and there are many ways to do that.

First of all, we create Canvas, Matrix and Bitmap during the first measurement. If the size of the View may change in your actual use scenario, we can also create it again for each measurement.

First declare three variables:

private lateinit var mBufferBitmap: Bitmap
private lateinit var mBufferCanvas: Canvas
private lateinit var mBufferMatrix: Matrix
Copy the code

Create onMeasure:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  // Measure code...
                                                                                            
  if (!this::mBufferBitmap.isInitialized) {
    mBufferBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    mBufferCanvas = Canvas(mBufferBitmap)
    mBufferMatrix = Matrix()
  }
}
Copy the code

Next we modify the message data model to store the animation progress of each message and some other properties for later expansion and modification.

data class Message(
  val avatar: String,// Avatar address
  val nickname: String,/ / nickname
  val joinRoom: Int.// 1= join, otherwise, exit
  var info: NicknameInfo,// Stores the width of this message
  var shader: BitmapShader? = null.// Image load related
  var bitmap: Bitmap? = null.var life: Int = 5.// Message survival time
  val timing: Long = System.currentTimeMillis(),// Time is spent
  var xProgress: Float = 1f.// The X-axis translation ratio ranges from 1.0F to 0.0f
  var yProgress: Float = 0f.// Y shift ratio, same as above
)
data class NicknameInfo(
  val nickname: String,// The nickname (longer than 5 characters, followed by an ellipsis)
  val nicknameWidth: Float.// Nickname width
  val messageWidth: Float.// Total message width
  val statusTextWidth: Float// The width of the words "join room" and "exit room". You can change this to a global variable if you want, because the word size is basically fixed
)
Copy the code

You can name and define data as you like, and store as much data as you need. Whether it is a custom View or other, it is always finally implemented on the data, define the structure and algorithm for storing data.

Based on the data we defined above, we use mBufferCanvas and mBufferMatrix to render a shading :p

private fun drawMessage(a) {
  mBufferMatrix.reset()// Reset the matrix before drawing to clear the buffer bitmap.
  // MbufferBitmap. eraseColor(color.transparent) is ok, but the difference between the two is not clear (;  ̄ legend  ̄), welcome message added
  mBufferCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
                                                                                        
  val msg = messageList[0]// In the previous article, we used a global variable, which I replaced with a List. Other forms are ok, because the requirement is to display two messages. Array is ok, and two global variables are ok, depending on your preference
  val info = msg.info
  val xOffset = msg.xProgress * info.messageWidth
  val yOffset = msg.yProgress * messageHeight + msg.yProgress * messagePadding
  mBufferMatrix.setTranslate(xOffset, yOffset)// The translation effect is implemented by matrix, or you can add the offset to the drawing directly, I did this originally, but the creation of mBufferMatrix is too redundant, I will use matrix instead (´ ω · ').
  mBufferCanvas.setMatrix(mBufferMatrix)// Remember to set
  drawMsg(// I lost the code to draw the message in a method, so I draw two and then call drawMsg once, I don't want to copy the code twice (´ · _ · ').
    msg,
    info.messageWidth,
    info.nicknameWidth,
    info.nickname
  )

  post { invalidate() }// Make a POST to prevent the child thread from calling
}

/** * DO NOT CALL THIS FUNCTION */
private fun drawMsg(
  msg: Message,
  messageWidth: Float,
  nicknameWidth: Float,
  nickname: String
) {
  path.reset()// Good habit to reset before use
  paint.color = Color.parseColor("#F3F3F3")

  val statusText = if (msg.joinRoom == 1) "Into the studio." else "Exit the studio."// The status can also be uploaded directly, personal preference
  // all the xOffset and yOffset you can see are the logic before matrix is used, delete 0f, uncomment x/yOffset, and then become the version without matrix (´ ω · ').
  val messageLeft = measuredWidth - messageWidth// + xOffset
  path.addArc(// Add a semicircle to path
    messageLeft,
    //yOffset,
    0f,
    messageLeft + avatarPadding + avatarHeight.toFloat(),
    /*yOffset*/0f + messageHeight.toFloat(),
    90f.180f
  )
  // Add a rectangle to 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")/ / the background color
  mBufferCanvas.drawPath(path, paint)/ / fill

  // Draw text
  paint.color = Color.WHITE
  mBufferCanvas.drawText(
    statusText,
    messageLeft + avatarHeight + avatarPadding.shl(1) + nicknameWidth + messagePadding,
    Measuredwidth-statustextwidth-statustextpadding) + /*xOffset*/ measuredwidth-Statustextwidth-Statustextpadding
    messageHeight.shr(1) + fontCenterOffset + /*yOffset*/0f,
    paint
  )
                                                                                                  
  paint.color = Color.parseColor("#BCBCBC")// Draw the nickname
  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 {// Once the image is loaded, draw the avatar
    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()
  }
}
Copy the code

All you need to do is change the X-axis and Y-axis variables and you are ready to “animate”. Try it (´ ω · ‘)

In the next article, we’ll implement adding messages, timing, deleting messages at the end of life, and real animation.