Android開發之逐幀動畫優化
阿新 • • 發佈:2019-02-17
Android上如果使用逐幀動畫的話,可以很方便地使用AnimationDrawable,無論是先宣告xml還是直接程式碼裡設定,都是幾分鐘的事,但使用AnimationDrawable有一個致命的弱點,那就是需要一次性載入所有圖片到記憶體,萬一幀數多了或者每張圖片都比較大,很容易就報out of memory的異常了,所以有必要進行優化。
這裡我們利用View.postDelayed方法延時替換圖片,這樣就能做到逐幀動畫的效果了,然後在替換圖片之前,強制回收ImageView當前bitmap就可以減少記憶體消耗了,廢話少說,上程式碼。
上面的類提供了兩種方法,迴圈播放和只播放一次,stopPlay是停止當前動畫,而mLastPlayFrameNo是當前圖片是所有圖片中的第幾張,迴圈中噹噹前的frameNo不等於mLastPlayFrameNo時回收圖片,這個相當重要,處理不當可能會報出使用回收後的bitmap的異常,因為有可能使用者一開始ImageView設定的src就是第0張,又或者使用者停止動畫後又想重新播放,那麼就會發生上面的情況。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; } }
好了,講述完這個類,看一下如何使用吧,很簡單。
SceneAnimation waitAnim = new SceneAnimation(waitImageView, waitResIds, 100); // 指定繫結的ImageView和圖片資源陣列以及每張圖片的延時
waitAnim.playConstant(); // 迴圈播放
waitAnim.stopPlay(); // 停止播放
逐幀動畫優化到這裡結束了,後期我們或許可以繼續優化,就是防止一個圖片幀太大,載入時間過長,我們可以快取多張,而不是現在的只快取一張。