# LottieAnimationView
###### tags: `Animation`
[TOC]
# Introduction
We usually think of gif when we plan to make animations, but actually gif size is quite large. For Instance, we try to find another type of animation file called **json**. This one can be displayed by **airbnb LottieAnimationView**.
**Note: Lottie VS GIF**
https://www.templatemonster.com/blog/lottie-vs-gif/
**JSON animation file** not only are smaller but also more preservable and customizable. Therefore, they are a good choice for use.
# Implementation
To use LottieAnimationView, we have to import it into our project.
```kotlin=
implementation 'com.airbnb.android:lottie:3.5.0'
```
**For Latest Version Check:**
https://github.com/airbnb/lottie-android
# Ways to load JSON animation
## Json File in Project
```kotlin=
setAnimation("test.json")
```
## Json by String
```kotlin=
val json: String = "Input Json file format"
setAnimationFromJson(json, null);
```
## JSON Url
```kotlin=
val jsonUrl: String = "Input Json Url"
setAnimationFromUrl(jsonUrl, null);
```
### Avoid JSON Url Crash
Be aware that it may crash if we just use this way to setAnimation, because if the url is empty or cannot be load, crash will probably occur.
Therefore, we will use **LottieTask<LottieComposition>**, to check for url loading response.
>**1.** We use its composition, to check if animation is composed succesfully.
>**2.** We then apply **addFailureListener && addListener** to check for loading response.[color=pink]
```kotlin=
val lottieTask: LottieTask<LottieComposition>
lottieTask = if (animJson.isNotEmpty()) {
LottieCompositionFactory.fromUrl(context, currentAnim)
} else {
//Deal what to do, if animation from url cannot be composed due to empty link
checkNextAnim()
return
}
lottieTask.apply {
addFailureListener {
//Deal if url animation cannot be load.
checkNextAnim()
}
//SUCCESS LISTENER
addListener {
setAnimationFromUrl(currentAnim)
playAnimation()
}
}
```
# Check Animation Status
We can check whether animation has started, ended, repeated or even canceled.
In the case below, we do not see the cancelled listener, because we actually did not use it. For instance, we have made an abstract class **SimpleAnimator** containing the things we actually need.
```kotlin=
abstract class SimpleAnimator() : Animator.AnimatorListener{
override fun onAnimationCancel(p0: Animator?) {}
}
```
**Code Example:**
```kotlin=
addAnimatorListener(object : SimpleAnimator() {
override fun onAnimationStart(animator: Animator) {
Log.d("ANIM_CHECK", "START $currentAnim")
showLottie()
}
override fun onAnimationEnd(animator: Animator) {
checkNextAnim()
hideLottie()
}
override fun onAnimationRepeat(animator: Animator) {
Log.d("ANIM_CHECK", "REPEAT " + animator.isRunning)
}
})
```
# Custom LottieAnimationView Example
This LottieAnimationView was made to be able to display the queued animations from AnimQueueHelper.
```kotlin=
import android.animation.Animator
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieTask
import com.solar.beststar.tools.AnimQueueHelper
import com.solar.beststar.tools.PrefHelper
import com.solar.beststar.tools.ViewHelper
import com.solar.beststar.tools.simplicity.SimpleAnimator
import kotlin.math.roundToInt
@Suppress("PrivatePropertyName")
class ComboLottieAnim : LottieAnimationView {
private val animHelper = AnimQueueHelper()
private var isChatSection = true
private var currentAnim: String? = null //Test Use
private lateinit var alphaCallback: AlphaCallback
interface AlphaCallback{
fun onAlphaChange(isShown: Boolean)
}
constructor(context: Context?) : super(context) { init() }
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init() }
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() }
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = measuredWidth
//force a 16:9 aspect ratio
val height = (width * .5625f).roundToInt()
setMeasuredDimension(width, height)
}
private fun init(){
repeatCount = 0
setAnimationListener()
setClickListener()
}
fun setAlphaCallback(alphaCallback: AlphaCallback){
this.alphaCallback = alphaCallback
}
fun pauseAnim(){
Log.d("ANIM_CHECK", "pauseAnim")
setStatus(::pauseAnimation, false)
}
fun resumeAnim(){
Log.d("ANIM_CHECK", "resumeAnim")
setStatus(::resumeAction, true)
}
private fun resumeAction(){
if(animHelper.needAnimStart()){
Log.d("ANIM_CHECK", "resumeAction: runAnimQueue")
runAnimQueue()
} else {
Log.d("ANIM_CHECK", "resumeAction: resumeAnimation")
resumeAnimation()
}
}
private fun setStatus(statusChange: () -> Unit, isShown: Boolean){
switchAnim(isShown)
statusChange()
}
fun runCombo(animJson: String){
animHelper.addQueue(animJson)
if (animHelper.needAnimStart()) {
runAnimQueue()
}
}
fun addQueue(animJson: String){
animHelper.addQueue(animJson)
}
private fun runAnimQueue() {
Log.d("ANIM_CHECK", "ANIM QUEUE=======")
clearAnimation()
val animJson = animHelper.dealNextAnim()
currentAnim = animJson
val lottieTask: LottieTask<LottieComposition>
lottieTask = if (animJson.isNotEmpty()) {
Log.d("ANIM_CHECK", "LottieCompositionFactory")
LottieCompositionFactory.fromUrl(context, currentAnim)
} else {
Log.d("ANIM_CHECK", "checkNextAnim")
checkNextAnim()
return
}
lottieTask.apply {
addFailureListener {
Log.d("ANIM_CHECK", "addFailureListener $currentAnim")
checkNextAnim()
}
//SUCCESS LISTENER
addListener {
Log.d("ANIM_CHECK", "addListener $currentAnim")
setAnimationFromUrl(currentAnim)
playAnimation()
}
}
}
private fun setAnimationListener() {
addAnimatorListener(object : SimpleAnimator() {
override fun onAnimationStart(animator: Animator) {
Log.d("ANIM_CHECK", "START $currentAnim")
showLottie()
}
override fun onAnimationEnd(animator: Animator) {
checkNextAnim()
hideLottie()
}
override fun onAnimationRepeat(animator: Animator) {
Log.d("ANIM_CHECK", "REPEAT " + animator.isRunning)
}
})
}
private fun setClickListener(){
setOnClickListener {
cancelAnimation()
checkNextAnim()
Log.d("ANIM_CHECK", "cancelAnimation")
}
}
private fun hideLottie() = setAlpha(0.0f, false)
private fun showLottie() = setAlpha(1.0f, true)
fun setSectionDisplay(isChatSection: Boolean){
this.isChatSection = isChatSection
switchAnim(isChatSection)
}
fun switchAnim(isShown: Boolean) : Boolean{
if(isShown && animHelper.checkRunStatus()){
showLottie()
return true
}
hideLottie()
return false
}
private fun setAlpha(alphaChange: Float, isShown: Boolean){
if(!PrefHelper.getSwitchAnim()){ return; }
if(!isChatSection && !ViewHelper.isVisible(alpha)){ return; }
Log.d("ANIM_CHECK", "setAlpha " + alphaChange + " isShown: " + isShown)
alpha = alphaChange
alphaCallback.onAlphaChange(isShown)
isFocusable = isShown
isFocusableInTouchMode = isShown
isClickable = isShown
}
private fun checkNextAnim() {
if (animHelper.isQueueAvailable()) {
Log.d("ANIM_CHECK", "END & START")
runAnimQueue()
} else {
Log.d("ANIM_CHECK", "END ")
animHelper.stopRun()
hideLottie()
}
}
}
```
# Reference
https://github.com/airbnb/lottie-android