current position:Home>Android technology sharing | [study room] custom view instead of notification animation (end)

Android technology sharing | [study room] custom view instead of notification animation (end)

2022-01-26 22:09:54 anyRTC

In the previous article, we implemented customization View Basic functions required , In this article, we pass Timer Realize animation function . I secretly modified some data structures , Post it below later .

Final rendering :
 Insert picture description here

Animation is through Timer Every time 17 Millisecond call View#post To call the main thread to update a frame . Define a interpolator Make the animation more natural ( The effect of gradual deceleration ).

First, define a data structure for storing and executing animation :

private data class AnimInfo(
  val block: (percentage: Float) -> Unit,//  Each frame call 
  val duration: Long = 510,
  val progress: Long = 0L,
  val done: () -> Unit = {}//  Call... At the end of the animation 
)

There are also modified data structures related to storing messages :

data class Message(
  val avatar: String,//  Head portrait 
  val nickname: String,//  nickname 
  val joinRoom: Int = 1,// 1= Join in , Others are exit 
  var shader: BitmapShader? = null,
  var bitmap: Bitmap? = null,
  var life: Long = 0L,//  current time 
  val lifeTime: Long = 5000L, //  Maximum lifetime 
)

Use linked lists to store Message and AnimInfo data :

private val animArr = LinkedList<AnimInfo>()
private val dataArr = LinkedList<Message>()

Use one Timer Timing animation and update Message Has existed for . stay init method :

init {
  paint.textSize = fontSize.toFloat()
  paint.style = Paint.Style.FILL
  val metrics = paint.fontMetrics
  fontCenterOffset = (abs(metrics.top) - metrics.bottom) / 2f
                                                                
  timer = Timer()
  timer.schedule(object : TimerTask() {
    override fun run() {
      if (dataArr.isNotEmpty()) {//  There is time to time 
        dataArr.forEach {
          it.life += 17L
        }
        val first = dataArr.first
        if (first.life >= first.lifeTime) {//  When the first item exceeds the maximum existence time, remove 
          dismissFirstMessage(true)
        }
      }

      if (animArr.isEmpty()) {//  If no animation is registered, skip directly 
        return
      }

      val i = animArr.iterator()//  Serialization is easier to remove 
      while (i.hasNext()) {
        val next = i.next()
        next.progress += 17L

        var percentage = next.progress.toFloat() / next.duration
        if (percentage > 1.0f)
          percentage = 1.0f
        post { next.block.invoke(interpolator(percentage)) }//  Each frame call 

        if (next.progress >= next.duration) {//  When the animation is finished, call  done  And remove yourself 
          post { next.done.invoke() }
          i.remove()
        }
      }
    }
  }, 0, 17)
}

interpolator The implementation of the :

private fun interpolator(x: Float): Float = (1.0f - (1.0f - x) * (1.0f - x))

Remember in onDetachedFromWindow Lieutenant general timer Task logoff :

override fun onDetachedFromWindow() {
  timer.cancel()
  timer.purge()
  super.onDetachedFromWindow()
}

Definition registerAnimator Method , Make opening an animation more ceremonial ( No

private fun registerAnimator(animInfo: AnimInfo) {
  animArr.add(animInfo)
}

Deleted drawMessage Method , Added addMessage Methods and removeFirstMessage Method .

addMessage Method :

fun addMessage(msg: Message) {
  if (!this::mBufferBitmap.isInitialized) {//  Initialization has not been completed yet  post  Wait for initialization 
    post { addMessage(msg) }
    return
  }

  //  If the number of notifications in animation execution or currently displayed has reached the maximum, it will be added to  waitList  in , Waiting for execution 
  if (animRunning || dataArr.size == limitMessageSize) {
    if (dataArr.size == limitMessageSize)
      dismissFirstMessage()
    waitList.add(msg)
    return
  }
                                                                                     
  animRunning = true
  dataArr.add(msg)
                                                                                     
  val nicknameWidth = paint.measureText(msg.nickname)
  val msgWidth = nicknameWidth + basedMessageWidth
                                                                                     
  loadImage(msg.avatar) { bitmap, b ->
    if (!b) [email protected]
                                                                                     
    val shader = BitmapShader(bitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    msg.let {
      it.bitmap = bitmap
      it.shader = shader
    }
  }

  //  Only the new one is updated here , So there is no need to empty the previously drawn data 
  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()
  }) {
    //  After the animation, first judge whether there are tasks waiting to be deleted , Then judge whether there are tasks waiting to be added 
    animRunning = false
    if (waitingRemove > 0) {
      removeFirstMessage()
    } else if (waitList.isNotEmpty()) {
      addMessage(waitList.removeFirst())
    }
  })
}

removeFirstMessage Method :

// timer  Every time 17 Polling once in milliseconds , If the animation is in execution, it will lead to  waitingRemove  Add a lot 
//  therefore  timer  Incoming messages do not increase the number of times waiting to be deleted 
fun removeFirstMessage(isFromTimer: Boolean = false) {
  if (dataArr.isEmpty())
    return

  if (animRunning) {
    if (!isFromTimer) waitingRemove++
    return
  }

  animRunning = true
  registerAnimator(AnimInfo({ percentage ->
    //  Because it changes two pieces of data and shifts up and down , You need to clear the last drawing 
    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) {//  First determine whether there are messages waiting to be added , And  addMessage  Just the opposite 
      addMessage(waitList.removeFirst())
    } else if (waitingRemove > 0) {
      if (dataArr.isNotEmpty()) {
        waitingRemove--
        removeFirstMessage()
        [email protected]
      }
      waitingRemove = 0
    }
  })
}

There are still some small problems that have not been handled well , For example, call continuously in a short time addMessage This will result in too many tasks waiting to be deleted ( Although it has been thoroughly handled ), For example, image loading is not interrupted .

If you want to go to the business environment, you need to improve these problems .

Source code address : Click here

 Insert picture description here

copyright notice
author[anyRTC],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201262209530062.html

Random recommended