1. 程式人生 > >Android 多執行緒載入幀動畫

Android 多執行緒載入幀動畫

幀動畫在使用時,很容易造成卡頓。此程式碼使用多執行緒動態載入,防止卡頓。

使用方法:

int[] right_res_id = new int[]{R.drawable.r_00072, R.drawable.r_00073, R.drawable.r_00074, R.drawable.r_00075};

final AnimationsContainer.FramesSequenceAnimation rightAnim =
                
      AnimationsContainer.getInstance().createProgressDialogAnim(imgRight,right_res_id);

rightAnim.setOnAnimStopListener(new AnimationsContainer.OnAnimationListener() {
            @Override
            public void onAnimationStart(AnimationsContainer.FramesSequenceAnimation animation) {
                //TODO
            }

            @Override
            public void onAnimationEnd(AnimationsContainer.FramesSequenceAnimation animation) {
                //TODO

            }

            @Override
            public void onAnimationStopOrCancel(AnimationsContainer.FramesSequenceAnimation animation) {
                //TODO
            }
        });
//rightAnim.setLoop(true);
//rightAnim.setGoBack(true); //只有動畫停止時才會呼叫
//rightAnim.setDelayMillis(58); //設定每幀播放時間,與setDuration(long)二選一
rightAnim.setDuration(3000); //設定播放總時間
rightAnim.start();

//rightAnim.stop();
//rightAnim.goBackStart(); //返回第一幀

//rightAnim.cancel();

程式碼AnimationsContainer.java

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.os.Handler;
import android.widget.ImageView;

import java.lang.ref.SoftReference;

/**
 * 多執行緒幀動畫載入類
 */

public class AnimationsContainer {

    private static final AnimationsContainer mInstance = new AnimationsContainer();

    private AnimationsContainer() {
    }

    /**
     * 獲取單例
     */
    public static AnimationsContainer getInstance() {
        return mInstance;
    }

    /**
     * 建立一個 Imageview 的播放動畫類
     *
     * @param imageView 要播放動畫的ImageView
     * @param resId     資源id陣列
     * @return progress dialog animation
     */
    public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView, int[] resId) {
        return new FramesSequenceAnimation(imageView, resId);
    }

    /**
     * 迴圈讀取幀---迴圈播放幀
     */
    public class FramesSequenceAnimation {
        private int[] mFrames; // 幀陣列
        private int mIndex; // 當前幀
        private boolean mShouldRun; // 開始/停止播放用
        private boolean mIsRunning; // 動畫是否正在播放,防止重複播放
        private SoftReference<ImageView> mSoftReferenceImageView; // 軟引用ImageView,以便及時釋放掉
        private Handler mHandler;
        private long mDelayMillis = 58;
        private boolean isLoop = false;
        private boolean isGoBack = true;
        private OnAnimationListener mOnAnimationListener; //播放停止監聽

        private Bitmap mBitmap = null;
        private BitmapFactory.Options mBitmapOptions;//Bitmap管理類,可有效減少Bitmap的OOM問題

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

            imageView.setImageResource(mFrames[0]);

            // 當圖片大小型別相同時進行復用,避免頻繁GC
            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();
                //設定Bitmap記憶體複用
                mBitmapOptions.inBitmap = mBitmap;//Bitmap複用記憶體塊,類似物件池,避免不必要的記憶體分配和回收
                mBitmapOptions.inMutable = true;//解碼時返回可變Bitmap
                mBitmapOptions.inSampleSize = 1;//縮放比例
            }
        }

        /**
         * 迴圈讀取下一幀
         */
        private int getNext() {
            mIndex++;
            if (mIndex < mFrames.length) {
                return mFrames[mIndex];
            } else if (mIndex >= mFrames.length && isLoop) {
                return mFrames[mIndex = 0];
            } else if (isGoBack) {
                end();
                return mFrames[mIndex = 0];
            } else {
                end();
                return mIndex = -1;
            }
        }

        /**
         * How long this animation should last. The duration cannot be negative.
         * 與<code>setDelayMillis(long)</code>方法二選一
         *
         * @param duration Duration in milliseconds
         * @throws java.lang.IllegalArgumentException if the duration is < 0
         * @attr ref android.R.styleable#Animation_duration
         * @see #setDelayMillis(long)
         */
        public FramesSequenceAnimation setDuration(long duration) {
            if (mFrames.length == 0) {
                throw new IllegalArgumentException("Animation frame length == 0");
            }
            if (duration < 0) {
                throw new IllegalArgumentException("Animators cannot have negative duration: " +
                        duration);
            }
            mDelayMillis = duration / mFrames.length;
            return this;
        }

        /**
         * 直接設定每幀間隔時間,與 <code>setDuration(long)</code> 方法二選一
         *
         * @param delayMillis 每幀間隔時間,數值推薦:58,單位:毫秒
         * @throws java.lang.IllegalArgumentException if the duration is < 0
         * @attr ref android.R.styleable#Animation_duration
         * @see #setDuration(long)
         */
        public FramesSequenceAnimation setDelayMillis(long delayMillis) {
            if (delayMillis < 0) {
                throw new IllegalArgumentException("Animators cannot have negative duration: " +
                        delayMillis);
            }
            mDelayMillis = delayMillis;
            return this;
        }

        /**
         * 是否迴圈播放
         *
         * @param isLoop
         */
        public synchronized FramesSequenceAnimation setLoop(boolean isLoop) {
            this.isLoop = isLoop;
            return this;
        }

        /**
         * 是否返回第一幀
         *
         * @param isGoBack
         */
        public synchronized FramesSequenceAnimation setGoBack(boolean isGoBack) {
            this.isGoBack = isGoBack;
            return this;
        }

        /**
         * 播放動畫,同步鎖防止多執行緒讀幀時,資料安全問題
         */
        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;
                        return;
                    }

                    mIsRunning = true;
                    //新開執行緒去讀下一幀
                    mHandler.postDelayed(this, mDelayMillis);

                    if (imageView.isShown()) {
                        int imageRes = getNext();
                        if (imageRes == -1) {
                            return;
                        }
                        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);

            if (mOnAnimationListener != null) {
                mOnAnimationListener.onAnimationStart(FramesSequenceAnimation.this);
            }
        }

        /**
         * 暫停播放,下次start會繼續播放
         */
        public synchronized void stop() {
            if (!mShouldRun) {
                return;
            }
            mShouldRun = false;
            if (mOnAnimationListener != null) {
                mOnAnimationListener.onAnimationStopOrCancel(FramesSequenceAnimation.this);
            }
        }

        /**
         * 取消播放,下次start會從頭播放
         */
        public synchronized void cancel() {
            if (!mShouldRun) {
                return;
            }
            mShouldRun = false;
            mIndex = 0;
            if (mOnAnimationListener != null) {
                mOnAnimationListener.onAnimationStopOrCancel(FramesSequenceAnimation.this);
            }
        }

        /**
         * 播放結束
         */
        private synchronized void end() {
            if (!mShouldRun) {
                return;
            }
            mShouldRun = false;
            if (mOnAnimationListener != null) {
                mOnAnimationListener.onAnimationEnd(FramesSequenceAnimation.this);
            }
        }

        /**
         * 直接返回第一幀
         */
        public synchronized void goBackStart() {
            if (mFrames.length == 0) {
                throw new IllegalArgumentException("Animation frame length == 0");
            }
            mIndex = 0;
            ImageView imageView = mSoftReferenceImageView.get();
            imageView.setImageResource(mFrames[mIndex]);
        }

        /**
         * 設定停止播放監聽
         *
         * @param listener
         */
        public FramesSequenceAnimation setOnAnimStopListener(OnAnimationListener listener) {
            this.mOnAnimationListener = listener;
            return this;
        }
    }

    /**
     * 停止播放監聽
     */
    public interface OnAnimationListener {
        void onAnimationStart(FramesSequenceAnimation animation);

        void onAnimationEnd(FramesSequenceAnimation animation);

        void onAnimationStopOrCancel(FramesSequenceAnimation animation);
    }
}