Home » Java » java – Making transitionX animation with frame by frame animation cause lags-Exceptionshub

java – Making transitionX animation with frame by frame animation cause lags-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I have an ImageView. When user presses a button I want my view to set an image from R.drawable(every time image is different) and then do translationX animation with frame-by-frame animation. For this purpose, I use ObjectAnimtor.OfFloat(..."translationX"...) and in doOnStart I call AnimationDrawable.start() on my image( I set animation-list there). Actually, on doOnEnd I put in my View another image (animation-list) and start another animation. After that, I make my image invisible and return it to the start point using translationX. Testing it on real device and on emulator causes lags at the start of the animation and in the moment when I change image. I have no idea how to fix it.

view.setImageResource(
            this.resources.getIdentifier(
                "${spellCasted}_anim",
                "drawable", context!!.packageName
            )
        )
        val x1 = startXposition
        val x2 = firstXdestination
        val frameAnimation = view.drawable as AnimationDrawable
        ObjectAnimator.ofFloat(
                view,
                "translationX",
                x1.toFloat(),
                x2.toFloat()
            ).apply {
                duration = 900
                interpolator = LinearInterpolator()
                doOnStart {
                    view.visibility = View.VISIBLE
                    frameAnimation.start()
                }
                doOnEnd {
                    if (hasPost(splitSpell)) { //hasPost(..) check is there additional image to translate from x2 to x2 + 20
                        frameAnimation.stop()
                        view.setImageResource(
                            context!!.resources.getIdentifier(
                                "${spellCasted}stop_anim",
                                "drawable", context!!.packageName
                            )
                        )
                        ObjectAnimator.ofFloat(
                                view, "translationX",
                                x2.toFloat(),
                                x2.toFloat() +  20f
                            )
                                .apply {
                                    duration = 1200
                                    interpolator = LinearInterpolator()
                                    doOnStart {
                                        val frameAnimationStop =
                                            view.drawable as AnimationDrawable
                                        frameAnimationStop.start()
                                    }
                                    doOnEnd {
                                        setDefault(view, x1, isEnemy) //returns view to startPos
                                    }
                                    start()
                                }
                    } else {
                        setDefault(view, x1, isEnemy)//returns view to startPos
                    }
                }
                start()
            }
How to&Answers:

If you look at the javadoc for imageView.setImageResource, you’ll see it says it will cause a latency hiccup because it loads the image on the UI thread.

I think you can fix this by pre-loading your next drawable in the background. I haven’t tried this because I don’t work with animation drawables, so sorry in advance if it doesn’t cure the hiccups.

Coroutines will make this a lot easier to set up and keep orderly. You can start by creating a function that eliminates the need for nested Animator callbacks:

suspend fun Animator.runToCompletion(): Unit = suspendCoroutine { cont ->
    doOnEnd { cont.resume(Unit) }
    start()
}

Then when you start the animation, you can start loading the next drawable in the background, and hopefully it will be ready by the time you need to show it:

val cxt = requireContext() // one source of failure if context is null

lifecycleScope.launch {

    // Start loading the second animation in a background thread
    val spellStopAnimationJob: Deferred<AnimationDrawable> = async(Dispatchers.Default) {
        cxt.getDrawable(ctx.resources.getIdentifier(
            "${spellCasted}stop_anim",
            "drawable", ctx.packageName
        ) as AnimationDrawable
    }

    view.setImageResource(
       resources.getIdentifier(
                "${spellCasted}_anim",
                "drawable", cxt.packageName
            )
    )
    val x1 = startXposition
    val x2 = firstXdestination
    val initialAnimation = view.drawable as AnimationDrawable
        .apply { start() }
    view.visibility = View.VISIBLE
    ObjectAnimator.ofFloat(
        view,
        "translationX",
        x1.toFloat(),
        x2.toFloat()
    ).apply {
        duration = 900L
        interpolator = LinearInterpolator()
    }.runToCompletion()

    if (hasPost(splitSpell)) { //hasPost(..) check is there additional image to translate from x2 to x2 + 20
        initialAnimation.stop()

        // Wait for the animation to be loaded (hopefully it's already done so no hiccup)
        val stopAnimation = spellStopAnimationJob.await()
            .apply { start() }
        view.setImageDrawable(stopAnimation)
        ObjectAnimator.ofFloat(
            view, "translationX",
            x2.toFloat(),
            x2.toFloat() + 20f
        ).apply {
            duration = 1200
            interpolator = LinearInterpolator()
        }.runToCompletion()
    }

    setDefault(view, x1, isEnemy)//returns view to startPos
}

By the way that line x2.toFloat() + 20f is going to mess you up when you run this on different devices, since you’re hard-coding an offset in pixels instead of DIP units. You should specify the number in DIP units and multiply by resources.displayMetrics.density to convert it to pixel units.