Most efficient way to show frame by frame animation android

前端 未结 8 1700
醉话见心
醉话见心 2021-02-01 07:41

I am trying to show frame by frame animation by changing images in a imageview. I tried animation drawable in xml and also changing the bitmap of the imageview inside a Handler.

8条回答
  •  深忆病人
    2021-02-01 08:18

    I wrote a simple activity that does the most basic thing:

    Loads all bitmaps in a Thread, then posts a change to an ImageView every 40ms.

    package mk.testanimation;
    
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.os.Handler;
    import android.util.Log;
    import android.widget.ImageView;
    import java.util.ArrayList;
    
    public class MainActivity extends Activity {
        ImageView mImageView;
    
        private int mImageRes[] = new int[]{
            R.drawable.s0,
            R.drawable.s1,
            R.drawable.s2,
            R.drawable.s3,
            R.drawable.s4,
            R.drawable.s5,
            R.drawable.s6,
            R.drawable.s7,
            R.drawable.s8,
            R.drawable.s9,
            R.drawable.s10,
            R.drawable.s11,
            R.drawable.s12,
            R.drawable.s13,
            R.drawable.s14,
            R.drawable.s15,
            R.drawable.s16,
            R.drawable.s17,
            R.drawable.s18,
            R.drawable.s19,
            R.drawable.s20,
            R.drawable.s21,
            R.drawable.s22,
            R.drawable.s23,
            R.drawable.s24,
            R.drawable.s25,
            R.drawable.s26,
            R.drawable.s27,
            R.drawable.s28,
            R.drawable.s29,
            R.drawable.s30,
            R.drawable.s31,
            R.drawable.s32,
            R.drawable.s33,
            R.drawable.s34,
            R.drawable.s35,
        };
    
        private ArrayList mBitmaps = new ArrayList(mImageRes.length);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final Handler handler = new Handler();
            mImageView = new ImageView(this);
            setContentView(mImageView);
            Thread important = new Thread() {
                @Override
                public void run() {
                    long timestamp = System.currentTimeMillis();
                    for (int i = 0; i < mImageRes.length; i++) {
                        mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
                    }
                    Log.d("ANIM-TAG", "Loading all bitmaps took " + (System.currentTimeMillis() - timestamp) + "ms");
                    for (int i = 0; i < mBitmaps.size(); i++) {
                        final int idx = i;
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                mImageView.setImageBitmap(mBitmaps.get(idx));
                            }
                        }, i * 40);
                    }
                }
            };
            important.setPriority(Thread.MAX_PRIORITY);
            important.start();
        }
    }
    

    This looked pretty decent on my Nexus 7, but it did take a little over 4s to load all the bitmaps.

    Can you load the bitmaps in advance?

    Also, it won't save a ton, but your pngs have a bunch of padding around the transparent space. You can crop them and reduce the memory a bit. Otherwise compressing the images will also help (like limiting the number of colors used).

    Ideally, in the above solution, you'd recycle the bitmaps immediately after they're no longer used.

    Also, if that's too memory-heavy, you could do as you mentioned, and have a Bitmap buffer, but I'm pretty sure it'll need to be more than 3 images large.

    Good luck.

    EDIT: Attempt 2. First, I cropped all the images to 590x590. This shaved about 1mb off the images. Then I created a new class, which is a bit "busy" and doesn't have a fixed frame rate but renders the images as soon as they are ready:

    package mk.testanimation;
    
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.os.Handler;
    import android.util.Log;
    import android.widget.ImageView;
    
    import java.util.ArrayList;
    
    public class MainActivity extends Activity {
        ImageView mImageView;
    
        private int mImageRes[] = new int[]{R.drawable.s0, R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5, R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10, R.drawable.s11, R.drawable.s12, R.drawable.s13, R.drawable.s14, R.drawable.s15, R.drawable.s16, R.drawable.s17, R.drawable.s18, R.drawable.s19, R.drawable.s20, R.drawable.s21, R.drawable.s22, R.drawable.s23, R.drawable.s24, R.drawable.s25, R.drawable.s26, R.drawable.s27, R.drawable.s28, R.drawable.s29, R.drawable.s30, R.drawable.s31, R.drawable.s32, R.drawable.s33, R.drawable.s34, R.drawable.s35};
    
        private ArrayList mBitmaps = new ArrayList(mImageRes.length);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final long timestamp = System.currentTimeMillis();
            final Handler handler = new Handler();
            mImageView = new ImageView(this);
            setContentView(mImageView);
            Thread important = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < mImageRes.length; i++) {
                        mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
                    }
                }
            };
            important.setPriority(Thread.MAX_PRIORITY);
            important.start();
            Thread drawing = new Thread() {
                @Override
                public void run() {
                    int i = 0;
                    while (i < mImageRes.length) {
                        if (i >= mBitmaps.size()) {
                            Thread.yield();
                        } else {
                            final Bitmap bitmap = mBitmaps.get(i);
                            handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mImageView.setImageBitmap(bitmap);
                                }
                            });
                            i++;
                        }
                    }
                    Log.d("ANIM-TAG", "Time to render all frames:" + (System.currentTimeMillis() - timestamp) + "ms");
                }
            };
            drawing.setPriority(Thread.MAX_PRIORITY);
            drawing.start();
        }
    }
    

    The above got the rendering to start nearly immediately, and it took less than 4s on my 2012 Nexus 7.

    As a last effort, I converted all the images to 8-bit PNGs instead of 32-bit. This brought the rendering to under 2 seconds!

    I'll bet any solution you end up with will benefit from making the images as small as possible.

    Again -- Good Luck!

提交回复
热议问题