1. 程式人生 > >Android開發之逐幀動畫優化

Android開發之逐幀動畫優化

Android上如果使用逐幀動畫的話,可以很方便地使用AnimationDrawable,無論是先宣告xml還是直接程式碼裡設定,都是幾分鐘的事,但使用AnimationDrawable有一個致命的弱點,那就是需要一次性載入所有圖片到記憶體,萬一幀數多了或者每張圖片都比較大,很容易就報out of memory的異常了,所以有必要進行優化。

這裡我們利用View.postDelayed方法延時替換圖片,這樣就能做到逐幀動畫的效果了,然後在替換圖片之前,強制回收ImageView當前bitmap就可以減少記憶體消耗了,廢話少說,上程式碼。

public class SceneAnimation {
    private ImageView mImageView;
    private int[] mFrameRess;
    private int[] mDurations;
    private int mDuration;
    private int mLastFrameNo;
    private long mBreakDelay = 0L;
    private int mLastPlayFrameNo = 0;
    private boolean isStop = true;

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

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

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

    @SuppressWarnings("unused")
    private void play(final int pFrameNo) {
        mImageView.postDelayed(new Runnable() {
            public void run() {
                if (pFrameNo != mLastPlayFrameNo) {
                    recycleImage();
                    mLastPlayFrameNo = pFrameNo;
                }
                mImageView.setBackgroundResource(mFrameRess[pFrameNo]);
                if (!isStop) {
                    if (pFrameNo == mLastFrameNo)
                        play(0);
                    else
                        play(pFrameNo + 1);
                }
            }
        }, mDurations[pFrameNo]);
    }

    private void playConstant(final int pFrameNo) {
        mImageView.postDelayed(new Runnable() {
            public void run() {
                if (pFrameNo != mLastPlayFrameNo) {
                    recycleImage();
                    mLastPlayFrameNo = pFrameNo;
                }
                mImageView.setBackgroundResource(mFrameRess[pFrameNo]);
                if (!isStop) {
                    if (pFrameNo == mLastFrameNo)
                        playConstant(0);
                    else
                        playConstant(pFrameNo + 1);
                }
            }
        }, pFrameNo == mLastFrameNo && mBreakDelay > 0 ? mBreakDelay
                : mDuration);
    }

    public void stopPlay() {
        isStop = true;
//        recycleImage();
    }

    public void playConstant() {
        isStop = false;
        playConstant(mLastPlayFrameNo);
    }

    private void recycleImage() {
        BitmapUtil.recycleImageView(mImageView);
    }

    public void playOnce(FinishCallback callback) {
        isStop = false;
        playOnce(callback, 0);
    }

    private void playOnce(FinishCallback callback, int frameNo) {
        mImageView.postDelayed(new Runnable() {
            public void run() {
                if (frameNo != 0)
                    recycleImage();
                mImageView.setBackgroundResource(mFrameRess[frameNo]);
                if (!isStop) {
                    if (frameNo == mLastFrameNo) {
                        isStop = true;
                        if (callback != null)
                            callback.onFinish(SceneAnimation.this);
                    } else
                        playOnce(callback, frameNo + 1);
                }
            }
        }, frameNo == mLastFrameNo && mBreakDelay > 0 ? mBreakDelay
                : mDuration);
    }

    public interface FinishCallback {
        public void onFinish(SceneAnimation sceneAnimation);
    }

    public boolean isRunning() {
        return !isStop;
    }
}
上面的類提供了兩種方法,迴圈播放和只播放一次,stopPlay是停止當前動畫,而mLastPlayFrameNo是當前圖片是所有圖片中的第幾張,迴圈中噹噹前的frameNo不等於mLastPlayFrameNo時回收圖片,這個相當重要,處理不當可能會報出使用回收後的bitmap的異常,因為有可能使用者一開始ImageView設定的src就是第0張,又或者使用者停止動畫後又想重新播放,那麼就會發生上面的情況。

好了,講述完這個類,看一下如何使用吧,很簡單。

SceneAnimation waitAnim = new SceneAnimation(waitImageView, waitResIds, 100); // 指定繫結的ImageView和圖片資源陣列以及每張圖片的延時
waitAnim.playConstant(); // 迴圈播放
waitAnim.stopPlay(); // 停止播放

逐幀動畫優化到這裡結束了,後期我們或許可以繼續優化,就是防止一個圖片幀太大,載入時間過長,我們可以快取多張,而不是現在的只快取一張。