简体   繁体   中英

Make an efficient repeating animation in android

I'm quite new to Android, and I have been trying to make a repeated animation (translation and crossfade) background of 4 images in my main activity. I learned about animations (and Animators particularly) before and in parallel to programming the animations, so I assume I did not find the most elegant, let alone efficient, way of doing that.

My main issue is that the application crashes with Out of Memory Error after several orientation changes of the device. I would like to find a way to fix that of course, but also make the whole thing elegant and memory efficient.

EDIT:

What I did basically, is using an AnimatorSet instance to play sequentially a set of translations and transitions (which are themselves an AnimatorSet composed of translation, translation + fade out, translation + fade in, etc..). Finally, I've added a listener that will play the animation again, every time it ends.

Here is my MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private AnimatorSet mAnimatorSet;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView backgroundOne = findViewById(R.id.background_main1);
        ImageView backgroundTwo = findViewById(R.id.background_main2);
        ImageView backgroundThree = findViewById(R.id.background_main3);
        ImageView backgroundFour = findViewById(R.id.background_main4);

        mAnimatorSet = BackgroundAnimator.set
                (backgroundOne, backgroundTwo, backgroundThree, backgroundFour);

        setImagesWidth(backgroundOne, backgroundTwo, backgroundThree, backgroundFour);


        mAnimatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                animation.start();
            }
        });

        mAnimatorSet.start();


    }

    private void setImagesWidth(ImageView... imageViews) {

        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);

        int width = size.x;
        int height = size.y;

        for (ImageView imageView : imageViews) {
            imageView.getLayoutParams().width = width + 800;
            imageView.getLayoutParams().height = height;
            imageView.requestLayout();
        }


    }

    public void onAddClick(View view) {

        startActivity(new Intent(this, AddItemActivity.class));
    }

    private static class BackgroundAnimator {

        private static AnimatorSet animatorSet;

        enum TranslateOptions {

            INITIAL,
            LEFT_TO_RIGHT,
            RIGHT_TO_LEFT
        }

        /**
         * set the animator
         */
        private static AnimatorSet set(ImageView viewOne, ImageView viewTwo,
                                       ImageView viewThree, ImageView viewFour) {


            ArrayList<Animator> list = new ArrayList<>();


            list.add(translate(viewOne, TranslateOptions.LEFT_TO_RIGHT));
            list.add(transitionLeftEnd(viewOne, viewTwo));
            list.add(translate(viewTwo, TranslateOptions.RIGHT_TO_LEFT));
            list.add(transitionRightEnd(viewTwo, viewThree));
            list.add(translate(viewThree, TranslateOptions.LEFT_TO_RIGHT));
            list.add(transitionLeftEnd(viewThree, viewFour));
            list.add(translate(viewFour, TranslateOptions.RIGHT_TO_LEFT));
            list.add(transitionRightEnd(viewFour, viewOne));


            animatorSet = new AnimatorSet();
            animatorSet.playSequentially(list);
            return animatorSet;

        }


        private static Animator translate(ImageView view, TranslateOptions option) {

            float startValue = 0, endValue = 0;

            switch (option) {

                case INITIAL:
                    startValue = -500;
                    endValue = 300;
                    break;

                case LEFT_TO_RIGHT: {
                    startValue = -300;
                    endValue = 300;
                    break;
                }
                case RIGHT_TO_LEFT: {
                    startValue = 300;
                    endValue = -300;
                    break;
                }

            }

            ObjectAnimator animator = ObjectAnimator.ofFloat
                    (view, "translationX", startValue, endValue);
            animator.setInterpolator(new LinearInterpolator());
            animator.setDuration(9000);

            return animator;
        }

        private static AnimatorSet transitionLeftEnd(ImageView viewOut, ImageView viewIn) {

            return transition(viewOut, viewIn, 300, 500);


        }


        private static AnimatorSet transitionRightEnd(ImageView viewOut, ImageView viewIn) {

            return transition(viewOut, viewIn, -300, -500);


        }

        private static AnimatorSet transition(ImageView viewOut, ImageView viewIn,
                                              float startValue, float endValue) {


            PropertyValuesHolder translateOut =
                    PropertyValuesHolder.ofFloat("translationX", startValue, endValue);
            PropertyValuesHolder fadeOut =
                    PropertyValuesHolder.ofFloat("alpha", 0);

            ObjectAnimator animatorOut = ObjectAnimator.ofPropertyValuesHolder(viewOut, translateOut, fadeOut);
            animatorOut.setInterpolator(new LinearInterpolator());
            animatorOut.setDuration(2250);

            PropertyValuesHolder translateIn =
                    PropertyValuesHolder.ofFloat
                            ("translationX", endValue, startValue);
            PropertyValuesHolder fadeIn =
                    PropertyValuesHolder.ofFloat("alpha", 1);

            ObjectAnimator animatorIn = ObjectAnimator.ofPropertyValuesHolder(viewIn, translateIn, fadeIn);
            animatorIn.setInterpolator(new LinearInterpolator());
            animatorIn.setDuration(2250);

            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.playTogether(animatorIn, animatorOut);


            return animatorSet;
        }
    }
}

Here is the error report from the logcat:

java.lang.OutOfMemoryError: Failed to allocate a 6144012 byte allocation with 2875952 free bytes and 2MB until OOM
        at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
        at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
        at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:446)
        at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:1080)
        at android.content.res.Resources.createFromResourceStream(Resources.java:2952)
        at android.content.res.Resources.loadDrawableForCookie(Resources.java:2684)
        at android.content.res.Resources.loadDrawable(Resources.java:2580)
        at android.content.res.MiuiResources.loadDrawable(MiuiResources.java:388)
        at android.content.res.Resources.getDrawable(Resources.java:824)
        at android.content.Context.getDrawable(Context.java:467)
        at android.support.v4.content.ContextCompat.getDrawable(ContextCompat.java:463)
        at android.support.v7.widget.AppCompatDrawableManager.getDrawable(AppCompatDrawableManager.java:203)
        at android.support.v7.widget.AppCompatDrawableManager.getDrawable(AppCompatDrawableManager.java:191)
        at android.support.v7.content.res.AppCompatResources.getDrawable(AppCompatResources.java:102)
        at android.support.v7.widget.AppCompatImageHelper.loadFromAttributes(AppCompatImageHelper.java:59)
        at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:78)
        at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:68)
        at android.support.v7.app.AppCompatViewInflater.createImageView(AppCompatViewInflater.java:182)
        at android.support.v7.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:106)
        at android.support.v7.app.AppCompatDelegateImpl.createView(AppCompatDelegateImpl.java:1266)
        at android.support.v7.app.AppCompatDelegateImpl.onCreateView(AppCompatDelegateImpl.java:1316)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:750)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:708)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:839)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:802)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:519)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:427)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
        at android.support.v7.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:469)
        at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)
        at com.tfreifeld.collectx.MainActivity.onCreate(MainActivity.java:30)
        at android.app.Activity.performCreate(Activity.java:6355)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1108)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2440)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2547)
        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4191)
        at android.app.ActivityThread.access$1200(ActivityThread.java:151)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1406)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:157)
        at android.app.ActivityThread.main(ActivityThread.java:5603)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:774)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:652)

Your memory leak is caused by the animations. Not only you are never stopping/cancelling the animations, you've set a listener on each animation to replay it when it finish.

Those animations have static references (in whatever is playing them) and will not be GC'ed until you cancel them (I would have removed the animation listeners as well when canceling). The animations themselves have references to the views, which have to the Activity context, etc...

In addition, BackgroundAnimation 's animatorSet is static and will leak at least one activity.

As to your general question about animations - you are doing several thing wrong. You will probably be better by using AnimatorSet.Builder for animations composition instead of AnimatorSet itself. In addition, there are much simpler ways than using ObjectAnimator such as AlphaAnimation and TranslateAnimation . And in general there are some repetitions in code, which can be extracted to methods.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM