Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Causing OutOfMemoryError in Frame by Frame Animation in Android

I am having lots of images as frames in my resources/drawable folder (let say approx 200). And using this images i want run a animation. The longest animation is of 80Frames. I am successfully able to run the animation on click of the buttons for some, but for some of the animation it is giving me OutOfMemoryError saying that VM can't provide such memory. It is out of VM Budget. I count the size of all of the images its about 10MB. The size of each image is 320x480 in pixels.

I try googling and found that i need to explicitly call the Garbage Collector using System.gc() method. I have done that but still i am getting some time error of memory. Can anyone please kindly help me out in this.

Some Code:-

ImageView img = (ImageView)findViewById(R.id.xxx);
img.setBackgroundResource(R.anim.angry_tail_animation);
AnimationDrawable mailAnimation = (AnimationDrawable) img.getBackground();
MediaPlayer player = MediaPlayer.create(this.getApplicationContext(), R.raw.angry);
    if(mailAnimation.isRunning()) {
    mailAnimation.stop();
    mailAnimation.start();
        if (player.isPlaying()) {
        player.stop();
        player.start();
    }
    else {
        player.start();
    }
}
else {
    mailAnimation.start();
        if (player.isPlaying()) {
        player.stop();
        player.start();
    }
    else {
        player.start();
    }
}

This is the code i have written in on click of a Button.....

Resource file inside res/drawable/anim

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true" >

<item android:drawable="@drawable/cat_angry0000" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0001" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0002" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0003" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0004" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0005" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0006" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0007" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0008" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0009" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0010" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0011" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0012" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0013" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0014" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0015" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0016" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0017" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0018" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0019" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0020" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0021" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0022" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0023" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0024" android:duration="50"/>

<item android:drawable="@drawable/cat_angry0025" android:duration="50"/>

</animation-list>

** The above is the resource file used in setBackgroundResource, same way I am having 10 more file for other different animation. **

Error Log

01-16 22:23:41.594: E/AndroidRuntime(399): FATAL EXCEPTION: main
01-16 22:23:41.594: E/AndroidRuntime(399): java.lang.IllegalStateException: Could not execute method of the activity
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.view.View$1.onClick(View.java:2144)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.view.View.performClick(View.java:2485)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.view.View$PerformClick.run(View.java:9080)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.os.Handler.handleCallback(Handler.java:587)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.os.Handler.dispatchMessage(Handler.java:92)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.os.Looper.loop(Looper.java:123)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.app.ActivityThread.main(ActivityThread.java:3683)
01-16 22:23:41.594: E/AndroidRuntime(399):  at java.lang.reflect.Method.invokeNative(Native Method)
01-16 22:23:41.594: E/AndroidRuntime(399):  at java.lang.reflect.Method.invoke(Method.java:507)
01-16 22:23:41.594: E/AndroidRuntime(399):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
01-16 22:23:41.594: E/AndroidRuntime(399):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
01-16 22:23:41.594: E/AndroidRuntime(399):  at dalvik.system.NativeStart.main(Native Method)
01-16 22:23:41.594: E/AndroidRuntime(399): Caused by: java.lang.reflect.InvocationTargetException
01-16 22:23:41.594: E/AndroidRuntime(399):  at java.lang.reflect.Method.invokeNative(Native Method)
01-16 22:23:41.594: E/AndroidRuntime(399):  at java.lang.reflect.Method.invoke(Method.java:507)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.view.View$1.onClick(View.java:2139)
01-16 22:23:41.594: E/AndroidRuntime(399):  ... 11 more
01-16 22:23:41.594: E/AndroidRuntime(399): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:460)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.content.res.Resources.loadDrawable(Resources.java:1709)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.content.res.Resources.getDrawable(Resources.java:581)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.drawable.AnimationDrawable.inflate(AnimationDrawable.java:267)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:787)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.graphics.drawable.Drawable.createFromXml(Drawable.java:728)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.content.res.Resources.loadDrawable(Resources.java:1694)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.content.res.Resources.getDrawable(Resources.java:581)
01-16 22:23:41.594: E/AndroidRuntime(399):  at android.view.View.setBackgroundResource(View.java:7533)
01-16 22:23:41.594: E/AndroidRuntime(399):  at talking.cat.CatActivity.middleButtonClicked(CatActivity.java:83)

Same way i have different buttons for different animation... Thanks

like image 235
Scorpion Avatar asked Jan 01 '12 08:01

Scorpion


4 Answers

I had the same problem. Android loads all the drawables at once, so animation with many frames causes this error.

I ended up creating my own simple sequence animation:

public class AnimationsContainer {
    public int FPS = 30;  // animation FPS

    // single instance procedures
    private static AnimationsContainer mInstance;

    private AnimationsContainer() {
    };

    public static AnimationsContainer getInstance() {
        if (mInstance == null)
            mInstance = new AnimationsContainer();
        return mInstance;
    }

    // animation progress dialog frames
    private int[] mProgressAnimFrames = { R.drawable.logo_30001, R.drawable.logo_30002, R.drawable.logo_30003 };

    // animation splash screen frames
    private int[] mSplashAnimFrames = { R.drawable.logo_ding200480001, R.drawable.logo_ding200480002 };


    /**
     * @param imageView 
     * @return progress dialog animation
     */
    public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView) {
        return new FramesSequenceAnimation(imageView, mProgressAnimFrames);
    }

    /**
     * @param imageView
     * @return splash screen animation
     */
    public FramesSequenceAnimation createSplashAnim(ImageView imageView) {
        return new FramesSequenceAnimation(imageView, mSplashAnimFrames);
    }

    /**
     * AnimationPlayer. Plays animation frames sequence in loop
     */
public class FramesSequenceAnimation {
    private int[] mFrames; // animation frames
    private int mIndex; // current frame
    private boolean mShouldRun; // true if the animation should continue running. Used to stop the animation
    private boolean mIsRunning; // true if the animation currently running. prevents starting the animation twice
    private SoftReference<ImageView> mSoftReferenceImageView; // Used to prevent holding ImageView when it should be dead.
    private Handler mHandler;
    private int mDelayMillis;
    private OnAnimationStoppedListener mOnAnimationStoppedListener;

    private Bitmap mBitmap = null;
    private BitmapFactory.Options mBitmapOptions;

    public FramesSequenceAnimation(ImageView imageView, int[] frames, int fps) {
        mHandler = new Handler();
        mFrames = frames;
        mIndex = -1;
        mSoftReferenceImageView = new SoftReference<ImageView>(imageView);
        mShouldRun = false;
        mIsRunning = false;
        mDelayMillis = 1000 / fps;

        imageView.setImageResource(mFrames[0]);

        // use in place bitmap to save GC work (when animation images are the same size & type)
        if (Build.VERSION.SDK_INT >= 11) {
            Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
            int width = bmp.getWidth();
            int height = bmp.getHeight();
            Bitmap.Config config = bmp.getConfig();
            mBitmap = Bitmap.createBitmap(width, height, config);
            mBitmapOptions = new BitmapFactory.Options();
            // setup bitmap reuse options. 
            mBitmapOptions.inBitmap = mBitmap;
            mBitmapOptions.inMutable = true;
            mBitmapOptions.inSampleSize = 1;
        }
    }

    private int getNext() {
        mIndex++;
        if (mIndex >= mFrames.length)
            mIndex = 0;
        return mFrames[mIndex];
    }

    /**
     * Starts the animation
     */
    public synchronized void start() {
        mShouldRun = true;
        if (mIsRunning)
            return;

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                ImageView imageView = mSoftReferenceImageView.get();
                if (!mShouldRun || imageView == null) {
                    mIsRunning = false;
                    if (mOnAnimationStoppedListener != null) {
                        mOnAnimationStoppedListener.AnimationStopped();
                    }
                    return;
                }

                mIsRunning = true;
                mHandler.postDelayed(this, mDelayMillis);

                if (imageView.isShown()) {
                    int imageRes = getNext();
                    if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
                        Bitmap bitmap = null;
                        try {
                            bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        if (bitmap != null) {
                            imageView.setImageBitmap(bitmap);
                        } else {
                            imageView.setImageResource(imageRes);
                            mBitmap.recycle();
                            mBitmap = null;
                        }
                    } else {
                        imageView.setImageResource(imageRes);
                    }
                }

            }
        };

        mHandler.post(runnable);
    }

        /**
         * Stops the animation
         */
        public synchronized void stop() {
            mShouldRun = false;
        }
    }
}

Usage:

FramesSequenceAnimation anim = AnimationsContainer.getInstance().createSplashAnim(mSplashImageView);
anim.start();
  • don't forget to stop it...
like image 144
Asaf Pinhassi Avatar answered Nov 19 '22 00:11

Asaf Pinhassi


I assume that your animation frame images are compressed (PNG or JPG). The compressed size is not useful for calculating how much memory is needed to display them. For that, you need to think about the uncompressed size. This will be the number of pixels (320x480) multiplied by the number of bytes per pixel, which is typically 4 (32 bits). For your images, then, each one will be 614,400 bytes. For the 26-frame animation example you provided, that will require a total of 15,974,400 bytes to hold the raw bitmap data for all the frames, not counting the object overhead.

Looking at the source code for AnimationDrawable, it appears to load all of the frames into memory at once, which it would basically have to do for good performance.

Whether you can allocate this much memory or not is very system dependent. I would at least recommend trying this on a real device instead of the emulator. You can also try tweaking the emulator's available RAM size, but this is just guessing.

There are ways to use BitmapFactory.inPreferredConfig to load bitmaps in a more memory-efficient format like RGB 565 (rather than ARGB 8888). This would save some space, but it still might not be enough.

If you can't allocate that much memory at once, you have to consider other options. Most high performance graphics applications (e.g. games) draw their graphics from combinations of smaller graphics (sprites) or 2D or 3D primitives (rectangles, triangles). Drawing a full-screen bitmap for every frame is effectively the same as rendering video; not necessarily the most efficient.

Does the entire content of your animation change with each frame? Another optimization could be to animate only the portion that actually changes, and chop up your bitmaps to account for that.

To summarize, you need to find a way to draw your animation using less memory. There are many options, but it depends a lot on how your animation needs to look.

like image 25
lyricsboy Avatar answered Nov 19 '22 02:11

lyricsboy


I spent a lot of time on this and have two different solutions, both good..

First, the problem: 1) Android loads all of the images into RAM, in uncompressed Bitmap format. 2) Android uses resource scaling, so on a phone with an xxxhdpi display (such as LG G3), each frame takes up a TON of space, so you quickly run out of RAM.

Solution #1

1) Bypasses Android's resource scaling. 2) Stores the bytearrays of all files in memory (these are small, especially for JPEGs). 3) Generates Bitmaps frame-by-frame, so it is almost impossible to run out of RAM.

Disadvantages: It spams your logs as Android is allocating memory for new Bitmaps and recycling old ones. It also performs lousy on older devices (Galaxy S1), but performs nicely on current budget phones (read: $10 Alcatel C1 I picked up at BestBuy). Second solution below performs better on older devices, but could still run out of RAM in some circumstances.

public class MyAnimationDrawable {
public static class MyFrame {
    byte[] bytes;
    int duration;
    Drawable drawable;
    boolean isReady = false;
}


public interface OnDrawableLoadedListener {
    public void onDrawableLoaded(List<MyFrame> myFrames);
}

public static void loadRaw(final int resourceId, final Context context, final OnDrawableLoadedListener onDrawableLoadedListener) {
    loadFromXml(resourceId, context, onDrawableLoadedListener);
}

private static void loadFromXml(final int resourceId, final Context context, final OnDrawableLoadedListener onDrawableLoadedListener) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            final ArrayList<MyFrame> myFrames = new ArrayList<>();

            XmlResourceParser parser = context.getResources().getXml(resourceId);

            try {
                int eventType = parser.getEventType();
                while (eventType != XmlPullParser.END_DOCUMENT) {
                    if (eventType == XmlPullParser.START_DOCUMENT) {

                    } else if (eventType == XmlPullParser.START_TAG) {

                        if (parser.getName().equals("item")) {
                            byte[] bytes = null;
                            int duration = 1000;

                            for (int i=0; i<parser.getAttributeCount(); i++) {
                                if (parser.getAttributeName(i).equals("drawable")) {
                                    int resId = Integer.parseInt(parser.getAttributeValue(i).substring(1));
                                    bytes = IOUtils.toByteArray(context.getResources().openRawResource(resId));
                                }
                                else if (parser.getAttributeName(i).equals("duration")) {
                                    duration = parser.getAttributeIntValue(i, 1000);
                                }
                            }

                            MyFrame myFrame = new MyFrame();
                            myFrame.bytes = bytes;
                            myFrame.duration = duration;
                            myFrames.add(myFrame);
                        }

                    } else if (eventType == XmlPullParser.END_TAG) {

                    } else if (eventType == XmlPullParser.TEXT) {

                    }

                    eventType = parser.next();
                }
            }
            catch (IOException | XmlPullParserException e) {
                e.printStackTrace();
            }

            // Run on UI Thread
            new Handler(context.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    if (onDrawableLoadedListener != null) {
                        onDrawableLoadedListener.onDrawableLoaded(myFrames);
                    }
                }
            });
        }
    }).run();
}

public static void animateRawManually(int resourceId, final ImageView imageView, final Runnable onStart, final Runnable onComplete) {
    loadRaw(resourceId, imageView.getContext(), new OnDrawableLoadedListener() {
        @Override
        public void onDrawableLoaded(List<MyFrame> myFrames) {
            if (onStart != null) {
                onStart.run();
            }

            animateRawManually(myFrames, imageView, onComplete);
        }
    });
}

public static void animateRawManually(List<MyFrame> myFrames, ImageView imageView, Runnable onComplete) {
    animateRawManually(myFrames, imageView, onComplete, 0);
}

private static void animateRawManually(final List<MyFrame> myFrames, final ImageView imageView, final Runnable onComplete, final int frameNumber) {
    final MyFrame thisFrame = myFrames.get(frameNumber);

    if (frameNumber == 0) {
        thisFrame.drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(thisFrame.bytes, 0, thisFrame.bytes.length));
    }
    else {
        MyFrame previousFrame = myFrames.get(frameNumber - 1);
        ((BitmapDrawable) previousFrame.drawable).getBitmap().recycle();
        previousFrame.drawable = null;
        previousFrame.isReady = false;
    }

    imageView.setImageDrawable(thisFrame.drawable);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            // Make sure ImageView hasn't been changed to a different Image in this time
            if (imageView.getDrawable() == thisFrame.drawable) {
                if (frameNumber + 1 < myFrames.size()) {
                    MyFrame nextFrame = myFrames.get(frameNumber+1);

                    if (nextFrame.isReady) {
                        // Animate next frame
                        animateRawManually(myFrames, imageView, onComplete, frameNumber + 1);
                    }
                    else {
                        nextFrame.isReady = true;
                    }
                }
                else {
                    if (onComplete != null) {
                        onComplete.run();
                    }
                }
            }
        }
    }, thisFrame.duration);

    // Load next frame
    if (frameNumber + 1 < myFrames.size()) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                MyFrame nextFrame = myFrames.get(frameNumber+1);
                nextFrame.drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(nextFrame.bytes, 0, nextFrame.bytes.length));
                if (nextFrame.isReady) {
                    // Animate next frame
                    animateRawManually(myFrames, imageView, onComplete, frameNumber + 1);
                }
                else {
                    nextFrame.isReady = true;
                }

            }
        }).run();
    }
}
}

** Solution #2 **

It loads the XML resource, parses it and loads the raw resources - thereby bypassing Android's resource scaling (which is responsible for most OutOfMemoryExceptions), and creates an AnimationDrawable.

Advantages: Performs better on older devices (eg. Galaxy S1)

Disadvantages: Can still run out of RAM as it's holding all of the uncompressed Bitmaps in memory (but they are smaller because they are not scaled the way Android normally scales images)

public static void animateManuallyFromRawResource(int animationDrawableResourceId, ImageView imageView, Runnable onStart, Runnable onComplete) {
    AnimationDrawable animationDrawable = new AnimationDrawable();

    XmlResourceParser parser = imageView.getContext().getResources().getXml(animationDrawableResourceId);

    try {
        int eventType = parser.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            if (eventType == XmlPullParser.START_DOCUMENT) {

            } else if (eventType == XmlPullParser.START_TAG) {

                if (parser.getName().equals("item")) {
                    Drawable drawable = null;
                    int duration = 1000;

                    for (int i=0; i<parser.getAttributeCount(); i++) {
                        if (parser.getAttributeName(i).equals("drawable")) {
                            int resId = Integer.parseInt(parser.getAttributeValue(i).substring(1));
                            byte[] bytes = IoUtils.readBytes(imageView.getContext().getResources().openRawResource(resId));
                            drawable = new BitmapDrawable(imageView.getContext().getResources(), BitmapFactory.decodeByteArray(bytes, 0, bytes.length));
                        }
                        else if (parser.getAttributeName(i).equals("duration")) {
                            duration = parser.getAttributeIntValue(i, 66);
                        }
                    }

                    animationDrawable.addFrame(drawable, duration);
                }

            } else if (eventType == XmlPullParser.END_TAG) {

            } else if (eventType == XmlPullParser.TEXT) {

            }

            eventType = parser.next();
        }
    }
    catch (IOException | XmlPullParserException e) {
        e.printStackTrace();
    }

    if (onStart != null) {
        onStart.run();
    }
    animateDrawableManually(animationDrawable, imageView, onComplete, 0);
}

private static void animateDrawableManually(final AnimationDrawable animationDrawable, final ImageView imageView, final Runnable onComplete, final int frameNumber) {
    final Drawable frame = animationDrawable.getFrame(frameNumber);
    imageView.setImageDrawable(frame);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            // Make sure ImageView hasn't been changed to a different Image in this time
            if (imageView.getDrawable() == frame) {
                if (frameNumber + 1 < animationDrawable.getNumberOfFrames()) {
                    // Animate next frame
                    animateDrawableManually(animationDrawable, imageView, onComplete, frameNumber + 1);
                }
                else {
                    // Animation complete
                    if (onComplete != null) {
                        onComplete.run();
                    }
                }
            }
        }
    }, animationDrawable.getDuration(frameNumber));
}

If you are still having memory issues, use smaller images... or store the resource name + duration, and generate the byte-array + Drawable on each frame. That would almost certainly cause too much chopping between frames, but uses almost zero RAM.

like image 11
Steven L Avatar answered Nov 19 '22 00:11

Steven L


I've created an animation class that displays frames based on passed in drawables resources and frames durations.

 protected class SceneAnimation{
    private ImageView mImageView;
    private int[] mFrameRess;
    private int[] mDurations;
    private int mDuration;

    private int mLastFrameNo;
    private long mBreakDelay;

 public SceneAnimation(ImageView pImageView, int[] pFrameRess, int[] pDurations){
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDurations = pDurations;
        mLastFrameNo = pFrameRess.length - 1;

        mImageView.setImageResource(mFrameRess[0]);
        play(1);
    }

    public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration){
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDuration = pDuration;
        mLastFrameNo = pFrameRess.length - 1;

        mImageView.setImageResource(mFrameRess[0]);
        playConstant(1);
    }

    public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration, long pBreakDelay){            
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDuration = pDuration;
        mLastFrameNo = pFrameRess.length - 1;
        mBreakDelay = pBreakDelay;

        mImageView.setImageResource(mFrameRess[0]);
        playConstant(1);
    }


    private void play(final int pFrameNo){
        mImageView.postDelayed(new Runnable(){
            public void run() {
                mImageView.setImageResource(mFrameRess[pFrameNo]);
                if(pFrameNo == mLastFrameNo)
                    play(0);
                else
                    play(pFrameNo + 1);
            }
        }, mDurations[pFrameNo]);
    }


    private void playConstant(final int pFrameNo){
        mImageView.postDelayed(new Runnable(){
            public void run() {                    
                mImageView.setImageResource(mFrameRess[pFrameNo]);

                if(pFrameNo == mLastFrameNo)
                    playConstant(0);
                else
                    playConstant(pFrameNo + 1);
            }
        }, pFrameNo==mLastFrameNo && mBreakDelay>0 ? mBreakDelay : mDuration);
    }        
};

It is used like this:

 private ImageView mTapScreenTextAnimImgView;    
private final int[] mTapScreenTextAnimRes = {R.drawable.tap0001_b, R.drawable.tap0002_b, R.drawable.tap0003_b, 
        R.drawable.tap0004_b, R.drawable.tap0005_b, R.drawable.tap0006_b, R.drawable.tap0005_b, R.drawable.tap0004_b,
        R.drawable.tap0003_b, R.drawable.tap0002_b, R.drawable.tap0001_b};
private final int mTapScreenTextAnimDuration = 100;
private final int mTapScreenTextAnimBreak = 500;

and in onCreate:

 mTapScreenTextAnimImgView = (ImageView) findViewById(R.id.scene1AnimBottom);
    new SceneAnimation(mTapScreenTextAnimImgView, mTapScreenTextAnimRes, mTapScreenTextAnimDuration, mTapScreenTextAnimBreak);
like image 3
Yar Avatar answered Nov 19 '22 02:11

Yar