# 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