Search code examples
androidandroid-animation2d-gamesanimationdrawable

Run multiple animations without noticeable lag


I am developing a simple 2D game. The game starts with one animation, then the players taps on screen and another animation is supposed to run without a noticeable lag while switching to next animation. My first approach was to use AnimationDrawable with an animation-list of 40 PNGs[frames]. I wanted to use two such AnimationDrawables. This approach cause OOM due to android's behavior of loading all frames of an animation before playing it. Then I decided to split my single animation to 4 animations of 10 frames each. The first animation of 10 frames runs perfectly but when I play the second [immediately after first has stopped] I get OOM and app closes.

1) I came across this solution: https://stackoverflow.com/a/10993879/6699069 This looks like the perfect solution to my problem. But I don't know about OnAnimationStoppedListener object that is used in the solution. There's no info about it on developers.android.com or anywhere else. I wanted to directly ask @Pinhassi under comments but can't add a comment, thanks to 50 reputation limit.

Edit: I found almost similar solution here Works without any errors :D But has the same lag as I mentioned in 4) I have even set the delayMillis = 0; It still runs at roughly 10fps [observed]

2) The post is 5 years old, so I guess, now there should be a predefined class to use long Animations without causing OOMs. Please point me to its link if such a class exists.

3) A friend suggested me to use gifs instead of animation. But I guess there's no way to stop/play a gif programmatically. Moreover, a gif with 40 frames seems a fantasy. If someone knows to use gif as an equivalent of animation, please shade some light.

4) My older approach [when I didnt know about AnimationDrawable class] was to use a SurfaceView and draw 40 bitmaps on a canvas in a loop to simulate animation. It worked but the frame rate is annoyingly slow. It gets slower the moment a player taps the screen. Also, the frame rate is significantly different on different phones. Some people suggested me to reduce image resolution. I scaled down my images from 720p to 320p but it didn't make much difference. Any way to speed up animation with this approach?

I would be really grateful if someone can answer 1) and 2).


Solution

  • Your question is quite broad, so I will try to help a bit (not sure if it will give a full answer). When I make animations in Android, I try as much as possible to use the native views as the performance is almost always the best, even for older devices. If you require to do something more complicated, perhaps you can consider using the Animator class.

    ObjectAnimator is one of inheriting classes of Animator and it allow you to create a custom animation based on a single property of your object. My suggestion is to create a custom view which will have a property called animationFrame with values in the range 0-39. Then when the value is set the correct image is loaded as the background of the view. You can use a library like Glide to optimize the loading of the images.

    So your code should look something like this:

    private class MyAnimation extends View {
    
        private ObjectAnimator animator = new ObjectAnimator();
    
        public MyAnimation(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            animator.setTarget(this);
            // the value must match a function with the signature "void setAnimationFrame(int frame)" (using the magic of reflection)
            animator.setPropertyName("animationFrame"); 
            animator.setIntValues(0, 39);
            animator.setRepeatCount(ObjectAnimator.INFINITE);
            animator.setRepeatMode(ObjectAnimator.RESTART);
            animator.setDuration(1000 / 60 * 40);
            animator.setInterpolator(new LinearInterpolator());
    
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // If you need to do something at the end of the animation
                }
            });
        }
    
        public void startAnimation() {
            animator.start();
        }
    
        private void setAnimationFrame(int frame) {
            setBackground(getDrawableForFrame(frame));
        }
    
        private Drawable getDrawableForFrame(int frame) {
            // Here you implement the loading from Glide or any other library or custom code
            return null;
        }
    }