Android實(shí)現(xiàn)高性能的幀動(dòng)畫(huà)禮物播放效果
引言:我們都知道Android實(shí)現(xiàn)動(dòng)畫(huà)的常見(jiàn)方式有那么幾種矮嫉,比如屬性動(dòng)畫(huà)臊诊,值動(dòng)畫(huà)等俯渤,這些動(dòng)畫(huà)都能實(shí)現(xiàn)一定的動(dòng)畫(huà)效果,比如平移侨嘀,縮放和透明等臭挽,但是對(duì)于復(fù)雜的動(dòng)畫(huà)效果用這些去實(shí)現(xiàn)則顯得比較困難和麻煩,效果也不是很好咬腕,比如各種禮物動(dòng)畫(huà)等欢峰,這些動(dòng)畫(huà)肯定要求比較酷炫點(diǎn)的,而這些使用Android常規(guī)的動(dòng)畫(huà)是很難實(shí)現(xiàn)想要的效果的涨共。
如果要實(shí)現(xiàn)比較好的動(dòng)畫(huà)效果纽帖,實(shí)現(xiàn)方式有那么幾種:
- Lottie:Lottie是Airbnb開(kāi)源的一個(gè)面向 iOS、Android举反、React Native 的動(dòng)畫(huà)庫(kù)懊直,可實(shí)現(xiàn)非常復(fù)雜的動(dòng)畫(huà),使用也及其簡(jiǎn)單火鼻,極大釋放人力室囊,值得一試。目前QQ禮物就是使用了這個(gè)魁索。Lottie
- SVGA:這是YY開(kāi)源的一個(gè)動(dòng)畫(huà)框架融撞,占用資源少,動(dòng)畫(huà)文件大小也比較小粗蔚,集成很方便尝偎,目前虎牙直播等YY系在線上使用。SVGA
- GIF動(dòng)畫(huà):GIF動(dòng)畫(huà)實(shí)現(xiàn)簡(jiǎn)單,對(duì)于APP端來(lái)說(shuō)直接用glide加載就可以了致扯,Gif圖實(shí)質(zhì)上就是把一幀幀的靜態(tài)圖片打包到一起肤寝,打成一個(gè)壓縮包,實(shí)際上這個(gè)壓縮包一點(diǎn)都不小抖僵,在播放性能和內(nèi)存控制上還是差好多鲤看。
- 幀動(dòng)畫(huà):幀動(dòng)畫(huà)相比前面幾個(gè)來(lái)說(shuō)其實(shí)也不是最好的選擇,但是幀動(dòng)畫(huà)的好處是可以實(shí)現(xiàn)的非绸烧耄酷炫的動(dòng)畫(huà)效果刨摩,壞處是占用資源大寺晌,我們都知道圖片是最占用內(nèi)存的了世吨,使用幀動(dòng)畫(huà)相比前面幾個(gè)來(lái)說(shuō)占用內(nèi)存肯定是比較多的,這個(gè)就要求我們使用幀動(dòng)畫(huà)播放的時(shí)候要管理好內(nèi)存呻征,避免oom耘婚。
本來(lái)我是想使用Lottie動(dòng)畫(huà)或者SVGA實(shí)現(xiàn)動(dòng)畫(huà)效果的,畢竟占用內(nèi)存資源相當(dāng)少陆赋,一個(gè)文件可以說(shuō)才幾百K沐祷,而且實(shí)現(xiàn)方式都相當(dāng)簡(jiǎn)單,都封裝的很完善了攒岛,而且播放的禮物效果也還不錯(cuò)赖临,但是由于想要更好的動(dòng)畫(huà)效果,所以需要使用到幀動(dòng)畫(huà)灾锯,最后通過(guò)使用surfaceview實(shí)現(xiàn)了使用幀動(dòng)畫(huà)播放大量的圖片并且很好的控制了內(nèi)存兢榨,實(shí)現(xiàn)效果感覺(jué)不錯(cuò)所以就在這里做下總結(jié)和分享,參考顺饮。
效果:這里給了兩個(gè)播放效果吵聪,限制于素材,其中播放的星空?qǐng)D片都是從網(wǎng)絡(luò)上下載的兼雄,所以看起來(lái)不像第一個(gè)具有連貫性吟逝。
?實(shí)現(xiàn):如果用Android實(shí)現(xiàn)幀動(dòng)畫(huà)的API去實(shí)現(xiàn)禮物幀動(dòng)畫(huà),則必定會(huì)出現(xiàn)內(nèi)存溢出赦肋,因?yàn)閳D片比較大块攒,比較占內(nèi)存,內(nèi)存不好控制佃乘,而且播放性能也不是很好囱井,因?yàn)槎Y物幀動(dòng)畫(huà)一般幀數(shù)都會(huì)比較多,禮物幀動(dòng)畫(huà)圖片是不可能放到項(xiàng)目目錄下的恕稠,不然圖片資源比較大會(huì)加大apk的大小而且也不利于擴(kuò)展琅绅。在這里使用SurfaceView去實(shí)現(xiàn)禮物幀動(dòng)畫(huà)功能,效果還不錯(cuò)鹅巍,內(nèi)存控制的也比較好千扶,主要利用了SurfaceView不與其宿主窗口共享同一個(gè)繪圖表面以及它的雙緩沖等功能料祠,詳細(xì)看下面對(duì)SurfaceView的介紹。
我們可以判斷有WiFi的情況下在后臺(tái)從網(wǎng)絡(luò)上下載圖片等資源保存到SDCard中澎羞,沒(méi)有WiFi網(wǎng)絡(luò)的情況下比如點(diǎn)擊禮物播放或者發(fā)送禮物等情況下去下載相對(duì)應(yīng)的動(dòng)畫(huà)壓縮文件再去解壓髓绽。然后從SDCard中讀取圖片資源,如果有音樂(lè)則可以在播放動(dòng)畫(huà)的時(shí)候同時(shí)播放音樂(lè)妆绞,這個(gè)可以根據(jù)音樂(lè)時(shí)間設(shè)置播放動(dòng)畫(huà)的時(shí)間從而保持一致(根據(jù)音樂(lè)時(shí)間計(jì)算出圖片每幀之間的間隔時(shí)間)顺呕。這邊我就不寫(xiě)下載解壓之類的代碼了,直接把禮物文件放到手機(jī)的SDCard中括饶,禮物文件這些可以看下圖株茶,或者在最下面點(diǎn)擊前往Github下載代碼,我會(huì)把這些文件放到項(xiàng)目目錄中(frameAnimation文件夾)图焰,大家如果下載代碼查看效果可以直接將這些文件所在的文件夾放到手機(jī)的SDCard中启盛。
SurfaceView: 這里我簡(jiǎn)單的介紹下SurfaceView,它不與其宿主窗口共享同一個(gè)繪圖表面技羔,我們都知道宿主窗口比如Activity中的view的繪制是層次結(jié)構(gòu)的僵闯,里面的任何一個(gè)view改變都會(huì)導(dǎo)致整個(gè)視圖結(jié)構(gòu)重繪一次,對(duì)于復(fù)雜的動(dòng)畫(huà)效果效率比較低下藤滥,而SurfaceView自身?yè)碛歇?dú)立的繪圖表面鳖粟,這樣SurfaceView的UI就可以在一個(gè)獨(dú)立的線程中進(jìn)行行繪制,SurfaceView的窗口刷新的時(shí)候不需要重繪應(yīng)用程序的窗口拙绊,可以不占用主線程的資源從而可以實(shí)現(xiàn)比較復(fù)雜的UI效果向图。
要了解 SurfaceView ,還須了解它的另外兩個(gè)組件:Surface 和 SurfaceHolder时呀,他們?nèi)咧g的關(guān)系實(shí)質(zhì)上就是 MVC张漂,Model就是數(shù)據(jù)模型的意思也就是這里的Surface;View即視圖也就是這里的SurfaceView谨娜;SurfaceHolder很明顯可以理解為Controller(控制器)航攒。在SurfaceView中你可以通過(guò)SurfaceHolder接口訪問(wèn)它內(nèi)部的surface,而我們執(zhí)行繪制的方法就是操作這個(gè) Surface內(nèi)部的Canvas趴梢,處理Canvas畫(huà)的效果和動(dòng)畫(huà)漠畜,大小,像素等坞靶,getHolder()方法可以得到這個(gè)SurfaceHolder憔狞,通過(guò)SurfaceHolder來(lái)控制surface的尺寸和格式,或者修改監(jiān)視surface的變化等等彰阴●遥可以通過(guò)SurfaceHolder.Callback來(lái)監(jiān)聽(tīng)Surface的生命周期。
雙緩沖:SurfaceView在更新視圖時(shí)用了兩個(gè)Canvas,一張frontCanvas和一張backCanvas簇抵,每次實(shí)際顯示的是frontCanvas庆杜,backCanvas存儲(chǔ)的是上一次更改前的視圖,當(dāng)使用lockCanvas()獲取畫(huà)布時(shí)碟摆,得到的實(shí)際上是backCanvas而不是正在顯示的frontCanvas晃财,當(dāng)你在獲取到的backCanvas上繪制完成后,再使用unlockCanvasAndPost(canvas)提交backCanvas視圖典蜕,那么這張backCanvas將替換正在顯示的frontCanvas被顯示出來(lái)断盛,原來(lái)的frontCanvas將切換到后臺(tái)作為backCanvas,這樣做的好處是在繪制期間不會(huì)出現(xiàn)黑屏愉舔。想要詳細(xì)了解可以查看SurfaceView 與view區(qū)別詳解 钢猛。
代碼流程分析
GiftFragmentAnimationDirector director = new GiftFragmentAnimationDirector();
mGiftFrameAnimation = director.createGiftFrameAnimation(new GiftFrameAnimationBuilder(mSurfaceView));
首先我這邊通過(guò)建造者模式創(chuàng)建一個(gè)類GiftFrameAnimation,這個(gè)類就是具體的功能實(shí)現(xiàn)類了屑宠,我們先來(lái)看看GiftFrameAnimationBuilder厢洞。
public class GiftFrameAnimationBuilder implements IBuildGiftFragmentAnimation {
private final GiftFrameAnimation mGiftFrameAnimation;
public GiftFrameAnimationBuilder(SurfaceView surfaceView) {
//創(chuàng)建GiftFrameAnimation仇让,同時(shí)初始化SurfaceView
mGiftFrameAnimation = new GiftFrameAnimation(surfaceView);
}
@Override
public void buildSupportInBitmap() {
//支持inBitmap典奉,改善內(nèi)存抖動(dòng)的問(wèn)題
mGiftFrameAnimation.setSupportInBitmap(true);
}
@Override
public void buildCacheCount() {
//設(shè)置緩存圖片個(gè)數(shù)
mGiftFrameAnimation.setCacheCount(5);
}
@Override
public void buildScaleType() {
//設(shè)置圖片的展現(xiàn)形式
mGiftFrameAnimation.setScaleType(GiftFrameAnimation.SCALE_TYPE_CENTER_CROP);
}
@Override
public void buildtRepeatMode() {
// 設(shè)置圖片播放模式,播放一次或者重復(fù)播放
mGiftFrameAnimation.setRepeatMode(GiftFrameAnimation.MODE_ONCE);
}
@Override
public void buildFrameInterval() {
//設(shè)置幀動(dòng)畫(huà)每幀之間的時(shí)間間隔
mGiftFrameAnimation.setFrameInterval(50);
}
@Override
public void buildMatrix() {
// 給定繪制bitmap的matrix不能和設(shè)置ScaleType同時(shí)起作用
// mGiftFrameAnimation.setMatrix(null);
}
@Override
public GiftFrameAnimation createGiftFragmentAnimation() {
// 返回設(shè)置好的GiftFrameAnimation
return mGiftFrameAnimation;
}
}
在這里主要是對(duì)GiftFrameAnimation進(jìn)行了一些初始化丧叽,設(shè)置一些具體的屬性卫玖,比如初始化surfaceView、圖片播放的間隔時(shí)間踊淳,圖片的播放模式以及scaleType等假瞬,接下來(lái)我們就對(duì)這些配置具體分析。
public GiftFrameAnimation(SurfaceView surfaceView) {
this.mSurfaceView = surfaceView;
this.mSurfaceHolder = surfaceView.getHolder();
mCallBack = new MyCallBack();
mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
mSurfaceView.setZOrderOnTop(true);
mSurfaceHolder.addCallback(mCallBack);
mBitmapCache = new SparseArray<>();
mContext = surfaceView.getContext();
mDrawMatrix = new Matrix();
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
這是在創(chuàng)建GiftFrameAnimation的同時(shí)初始化SurfaceView迂尝,主要是獲取SurfaceHolder脱茉、SurfaceHolder.Callback、將SurfaceView背景設(shè)置為透明以及初始化緩存集合類和用于進(jìn)行繪制Bitmap的Matrix和Paint垄开。
public void setSupportInBitmap(boolean support) {
this.mSupportInBitmap = support;
}
這個(gè)是用來(lái)設(shè)置是否支持inBitmap琴许,支持inBitmap會(huì)非常顯著的改善內(nèi)存抖動(dòng)的問(wèn)題,因?yàn)榇嬖赽itmap復(fù)用的問(wèn)題溉躲,當(dāng)設(shè)置支持inBitmap時(shí)榜田,請(qǐng)務(wù)必保證幀動(dòng)畫(huà)所有的圖片分辨率和顏色位數(shù)完全一致。默認(rèn)為true锻梳,詳情可查看箭券。
public void setFrameInterval(int time) {
this.mFrameInterval = time;
}
設(shè)置幀動(dòng)畫(huà)圖片之間的播放間隔時(shí)間, 如果給定了播放總時(shí)間mFrameTime不為0疑枯,則這個(gè)設(shè)置無(wú)效辩块,要按照計(jì)算mFrmeTime / pictureNumber得出。因?yàn)槲以谖募械膉son文件中指定了圖片的播放總時(shí)間,這是為了有背景音樂(lè)時(shí)播放動(dòng)畫(huà)時(shí)間和背景播放音樂(lè)時(shí)間保持一致废亭。
public void setCacheCount(int count) {
this.mCacheCount = count;
}
這是設(shè)置緩存到內(nèi)存中的圖片的個(gè)數(shù)古今,便于圖片的快速加載繪制。
public void setMatrix(@NonNull Matrix matrix) {
mDrawMatrix = matrix;
mScaleType = SCALE_TYPE_MATRIX;
}
給定繪制bitmap的matrix滔以,不能和ScaleType同時(shí)起作用捉腥。
/**
* 表示給定的matrix
*/
private final int SCALE_TYPE_MATRIX = 0;
/**
* 完全拉伸,不保持原始圖片比例你画,鋪滿
*/
public static final int SCALE_TYPE_FIT_XY = 1;
/**
* 保持原始圖片比例抵碟,整體拉伸圖片至少填充滿X或者Y軸的一個(gè)
* 并最終依附在視圖的上方或者左方
*/
public static final int SCALE_TYPE_FIT_START = 2;
/**
* 保持原始圖片比例,整體拉伸圖片至少填充滿X或者Y軸的一個(gè)
* 并最終依附在視圖的中心
*/
public static final int SCALE_TYPE_FIT_CENTER = 3;
/**
* 保持原始圖片比例坏匪,整體拉伸圖片至少填充滿X或者Y軸的一個(gè)
* 并最終依附在視圖的下方或者右方
*/
public static final int SCALE_TYPE_FIT_END = 4;
/**
* 將圖片置于視圖中央拟逮,不縮放
*/
public static final int SCALE_TYPE_CENTER = 5;
/**
* 整體縮放圖片,保持原始比例适滓,將圖片置于視圖中央敦迄,
* 確保填充滿整個(gè)視圖,超出部分將會(huì)被裁剪
*/
public static final int SCALE_TYPE_CENTER_CROP = 6;
/**
* 整體縮放圖片凭迹,保持原始比例罚屋,將圖片置于視圖中央,
* 確保X或者Y至少有一個(gè)填充滿屏幕
*/
public static final int SCALE_TYPE_CENTER_INSIDE = 7;
@IntDef({SCALE_TYPE_FIT_XY, SCALE_TYPE_FIT_START, SCALE_TYPE_FIT_CENTER, SCALE_TYPE_FIT_END,
SCALE_TYPE_CENTER, SCALE_TYPE_CENTER_CROP, SCALE_TYPE_CENTER_INSIDE})
@Retention(RetentionPolicy.SOURCE)
public @interface ScaleType {
}
public void setScaleType(@ScaleType int type) {
this.mScaleType = type;
}
這是設(shè)置圖片的scaleType嗅绸,這邊主要定義了8種選擇脾猛,主要是通過(guò)下面的代碼根據(jù)指定的scaleType配置繪制bitmap的Matrix實(shí)現(xiàn)的。
private void configureDrawMatrix(Bitmap bitmap) {
final int srcWidth = bitmap.getWidth();
final int srcHeight = bitmap.getHeight();
final int dstWidth = mSurfaceView.getWidth();
final int dstHeight = mSurfaceView.getHeight();
final boolean nothingChanged = srcWidth == mLastFrameWidth
&& srcHeight == mLastFrameHeight
&& mLastFrameScaleType == mScaleType
&& mLastSurfaceWidth == dstWidth
&& mLastSurfaceHeight == dstHeight;
if (nothingChanged) {
return;
}
mLastFrameScaleType = mScaleType;
mLastFrameWidth = srcWidth;
mLastFrameHeight = srcHeight;
mLastSurfaceWidth = dstWidth;
mLastSurfaceHeight = dstHeight;
if (mScaleType == SCALE_TYPE_MATRIX) {
return;
} else if (mScaleType == SCALE_TYPE_CENTER) {
mDrawMatrix.setTranslate(
Math.round((dstWidth - srcWidth) * 0.5f),
Math.round((dstHeight - srcHeight) * 0.5f));
} else if (mScaleType == SCALE_TYPE_CENTER_CROP) {
float scale;
float dx = 0, dy = 0;
//按照高縮放
if (dstHeight * srcWidth > dstWidth * srcHeight) {
scale = (float) dstHeight / (float) srcHeight;
dx = (dstWidth - srcWidth * scale) * 0.5f;
} else {
scale = (float) dstWidth / (float) srcWidth;
dy = (dstHeight - srcHeight * scale) * 0.5f;
}
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else if (mScaleType == SCALE_TYPE_CENTER_INSIDE) {
float scale;
float dx;
float dy;
//小于dst時(shí)不縮放
if (srcWidth <= dstWidth && srcHeight <= dstHeight) {
scale = 1.0f;
} else {
scale = Math.min((float) dstWidth / (float) srcWidth,
(float) dstHeight / (float) srcHeight);
}
dx = Math.round((dstWidth - srcWidth * scale) * 0.5f);
dy = Math.round((dstHeight - srcHeight * scale) * 0.5f);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else {
RectF srcRect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
RectF dstRect = new RectF(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight());
mDrawMatrix.setRectToRect(srcRect, dstRect, MATRIX_SCALE_ARRAY[mScaleType - 1]);
}
}
private final Matrix.ScaleToFit[] MATRIX_SCALE_ARRAY = {
Matrix.ScaleToFit.FILL,
Matrix.ScaleToFit.START,
Matrix.ScaleToFit.CENTER,
Matrix.ScaleToFit.END
};
上面大致是介紹了一下GiftFrameAnimation一些設(shè)置鱼鸠,下面就來(lái)主要介紹下代碼功能實(shí)現(xiàn)的整體的流程分析猛拴。
mGiftFrameAnimation.start(giftId, 0);
public void start(String giftId, int startFramePositon) {
if (mCallBack.isDrawing) {
stop();
}
mPathList = getPathList(giftId);
if (mPathList == null || mPathList.size() == 0) {
return;
}
mTotalCount = mPathList.size();
//緩存圖片個(gè)數(shù)不能超過(guò)總圖片數(shù)
if (mCacheCount > mTotalCount) {
mCacheCount = mTotalCount;
}
mStartFramePositon = startFramePositon;
if (mStartFramePositon >= mTotalCount) {
mStartFramePositon = 0;
}
startDecodeThread();
}
這是開(kāi)始播放幀動(dòng)畫(huà),第一個(gè)參數(shù)giftId是獲取圖片文件的位置的標(biāo)志蚀狰,比如我保存在SD卡上的圖片文件名1000愉昆,那么我將giftId參數(shù)設(shè)為1000,則會(huì)去讀取這個(gè)文件夾下的圖片進(jìn)行播放麻蹋,下面我都指定之歌第二個(gè)參數(shù)播放開(kāi)始的圖片跛溉,比如傳0,也就是表示從第一個(gè)圖片開(kāi)始播放哥蔚。
private List<String> getPathList(String giftId) {
List<String> list = new ArrayList<>();
GiftModel giftModel = GainGiftUtils.getGiftModel(giftId);
if (giftModel == null) {
if (mAnimationStateListener != null) {
mAnimationStateListener.giftFileNotExists();
}
return list;
}
String backgroundColor = giftModel.backgroundColor;
String musicPath = giftModel.backgroundMusic;
list = giftModel.imageArray;
int size = list.size();
mFrmeTime = giftModel.playTimeMillisecond;
if (mFrmeTime == 0) {
mFrmeTime = mFrameInterval * size;
// 為了避免幀動(dòng)畫(huà)的播放跟背景音樂(lè)時(shí)長(zhǎng)不一致倒谷,故禁掉音樂(lè)播放
musicPath = null;
} else {
//根據(jù)json文件中給定的播放時(shí)間計(jì)算每幀圖片之間的播放間隔時(shí)間
mFrameInterval = mFrmeTime / size;
}
if (mAnimationStateListener != null) {
mAnimationStateListener.giftFrameType(musicPath, mFrmeTime, backgroundColor);
}
return list;
}
這個(gè)是獲取保存在SD卡上的圖片的路徑的集合,首先通過(guò)讀取1000.json文件中的json糙箍,然后進(jìn)行判斷json中所記錄的文件是否存在渤愁,可解析成GiftModel,這時(shí)可以獲取圖片路徑集合深夯、音樂(lè)文件路徑抖格,背景顏色以及播放總時(shí)間诺苹。
public class GiftModel {
public String backgroundMusic;
public int playTimeMillisecond;
public String backgroundColor;
public List<String> imageArray;
}
接下來(lái)我們?cè)賮?lái)看下startDecodeThread
private void startDecodeThread() {
new Thread() {
@Override
public void run() {
super.run();
Looper.prepare();
mDecodeHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == STOP_ANIMATION) {
decodeBitmap(STOP_ANIMATION);
getLooper().quit();
return;
}
decodeBitmap(msg.what);
}
};
decodeBitmap(START_ANIMATION);
Looper.loop();
}
}.start();
}
這里開(kāi)啟了一個(gè)線程去處理, 根據(jù)不同指令 進(jìn)行不同操作雹拄,START_ANIMATION表示開(kāi)始動(dòng)畫(huà)標(biāo)志收奔,STOP_ANIMATION表示動(dòng)畫(huà)結(jié)束標(biāo)志,至于decodeBitmap(msg.what)則是用來(lái)處理繼續(xù)緩存下個(gè)Bitmap的滓玖。
private void decodeBitmap(int position) {
if (position == START_ANIMATION) {
//初始化存儲(chǔ)
if (mSupportInBitmap) {
mOptions = new BitmapFactory.Options();
mOptions.inMutable = true;
mOptions.inSampleSize = 1;
}
for (int i = mStartFramePositon; i < mCacheCount + mStartFramePositon; i++) {
int putPosition = i;
if (putPosition > mTotalCount - 1) {
putPosition = putPosition % mTotalCount;
}
mBitmapCache.put(putPosition, decodeBitmapReal(mPathList.get(putPosition)));
}
mCallBack.startAnim();
} else if (position == STOP_ANIMATION) {
mCallBack.stopAnim();
} else if (mPlayMode == MODE_ONCE) {
if (position + mCacheCount <= mTotalCount - 1) {
//由于surface的雙緩沖坪哄,不能直接復(fù)用上一幀的bitmap,因?yàn)樯弦粠腷itmap可能還沒(méi)有post
writeInBitmap(position);
mBitmapCache.put(position + mCacheCount, decodeBitmapReal(mPathList.get(position + mCacheCount)));
}
//循環(huán)播放
} else if (mPlayMode == MODE_INFINITE) {
//由于surface的雙緩沖势篡,不能直接復(fù)用上一幀的bitmap翩肌,上一幀的bitmap可能還沒(méi)有post
writeInBitmap(position);
//播放到尾部時(shí),取mod
if (position + mCacheCount > mTotalCount - 1) {
mBitmapCache.put((position + mCacheCount) % mTotalCount, decodeBitmapReal(mPathList.get((position + mCacheCount) % mTotalCount)));
} else {
mBitmapCache.put(position + mCacheCount, decodeBitmapReal(mPathList.get(position + mCacheCount)));
}
}
}
先來(lái)看看START_ANIMATION開(kāi)始播放動(dòng)畫(huà)這塊禁悠,mStartFramePositon就是我們一開(kāi)始調(diào)用start的時(shí)候傳進(jìn)來(lái)的播放開(kāi)始位置念祭,這邊根據(jù)設(shè)置的緩存?zhèn)€數(shù)對(duì)圖片進(jìn)行了mBitmapCache緩存,然后調(diào)用了mCallBack.startAnim()碍侦,我們進(jìn)入看看
private void startAnim() {
if (mAnimationStateListener != null) {
mAnimationStateListener.onStart();
}
isDrawing = true;
position = mStartFramePositon;
//繪制線程
drawThread = new Thread() {
@Override
public void run() {
super.run();
while (isDrawing) {
try {
long now = System.currentTimeMillis();
drawBitmap();
//控制兩幀之間的間隔
sleep(mFrameInterval - (System.currentTimeMillis() - now) > 0 ? mFrameInterval - (System.currentTimeMillis() - now) : 0);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
};
drawThread.start();
}
可以看到這里開(kāi)啟了一個(gè)線程去執(zhí)行drawBitmap()粱坤,這里就是圖片的進(jìn)行繪制的方法了
private void drawBitmap() {
//當(dāng)循環(huán)播放時(shí),獲取真實(shí)的position
if (mPlayMode == MODE_INFINITE && position >= mTotalCount) {
position = position % mTotalCount;
}
if (position >= mTotalCount) {
mDecodeHandler.sendEmptyMessage(STOP_ANIMATION);
clearSurface();
return;
}
if (mBitmapCache.get(position, null) == null) {
stopAnim();
return;
}
final Bitmap currentBitmap = mBitmapCache.get(position);
mDecodeHandler.sendEmptyMessage(position);
mCanvas = mSurfaceHolder.lockCanvas();
if (mCanvas == null) {
return;
}
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
configureDrawMatrix(currentBitmap);
mCanvas.drawBitmap(currentBitmap, mDrawMatrix, mPaint);
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
position++;
}
這里首先獲取了播放的圖片位置position瓷产,首先判斷了當(dāng)前的播放模式是不是MODE_INFINITE重復(fù)播放站玄,如果是,則計(jì)算出當(dāng)前的播放位置拦英,避免超出圖片數(shù)蜒什,當(dāng)position大約等于圖片總數(shù)mTotalCount時(shí),則表示播放完畢了疤估,這時(shí)通過(guò)mDecodeHandler.sendEmptyMessage(STOP_ANIMATION)發(fā)送了一個(gè)停止動(dòng)畫(huà)標(biāo)志,這個(gè)在startDecodeThread()中進(jìn)行處理霎冯,也就是上面的decodeBitmap的position == STOP_ANIMATION铃拇;然后又發(fā)送了一個(gè)mDecodeHandler.sendEmptyMessage(position),也就是decodeBitmap(msg.what)通知緩存下一個(gè)圖片沈撞。
再來(lái)看看繪制過(guò)程慷荔,通過(guò)mSurfaceHolder.lockCanvas()獲取Surface的Canvas后,SurfaceView會(huì)利用Surface的一個(gè)同步鎖鎖住畫(huà)布Canvas缠俺,直到調(diào)用unlockCanvasAndPost(Canvas canvas)函數(shù)显晶,才解鎖畫(huà)布并提交改變,將圖形顯示壹士,這里的同步機(jī)制保證Surface的Canvas在繪制過(guò)程中不會(huì)被改變(被摧毀磷雇、修改),避免多個(gè)不同的線程同時(shí)操作同一個(gè)Canvas對(duì)象躏救。
我們往前看decodeBitmap()方法唯笙,接下來(lái)我們繼續(xù)看看position == STOP_ANIMATION螟蒸,也就是停止播放動(dòng)畫(huà),這里調(diào)用了mCallBack.stopAnim()崩掘。
private void stopAnim() {
if (!isDrawing) {
return;
}
isDrawing = false;
position = 0;
mBitmapCache.clear();
clearSurface();
if (mDecodeHandler != null) {
mDecodeHandler.sendEmptyMessage(STOP_ANIMATION);
}
if (drawThread != null) {
drawThread.interrupt();
}
if (mAnimationStateListener != null) {
mAnimationStateListener.onFinish();
}
mInBitmapFlag = 0;
mInBitmap = null;
}
private void clearSurface() {
try {
mCanvas = mSurfaceHolder.lockCanvas();
if (mCanvas != null) {
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
} catch (Exception e) {
e.printStackTrace();
}
}
停止播放動(dòng)畫(huà)這里主要是清除緩存七嫌,終止繪制線程,調(diào)用clearSurface()獲取mCanvas清理畫(huà)布苞慢。
在加載幀動(dòng)畫(huà)的同時(shí)會(huì)播放背景音樂(lè)诵原,這里附上加載播放音樂(lè)文件的MediaManager類。
public class MediaManager {
private static MediaPlayer mPlayer;
private static boolean isPause;
public static void playSound(String filePathString, OnCompletionListener onCompletionListener) {
// TODO Auto-generated method stub
if (mPlayer == null) {
mPlayer = new MediaPlayer();
//保險(xiǎn)起見(jiàn)挽放,設(shè)置報(bào)錯(cuò)監(jiān)聽(tīng)
mPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// TODO Auto-generated method stub
mPlayer.reset();
return false;
}
});
} else {
mPlayer.reset();//就回復(fù)
}
try {
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setOnCompletionListener(onCompletionListener);
mPlayer.setDataSource(filePathString);
mPlayer.prepare();
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void stop(){
if (mPlayer != null && mPlayer.isPlaying()) {
mPlayer.stop();
}
}
//停止函數(shù)
public static void pause() {
if (mPlayer != null && mPlayer.isPlaying()) {
mPlayer.pause();
isPause = true;
}
}
//繼續(xù)
public static void resume() {
if (mPlayer != null && isPause) {
mPlayer.start();
isPause = false;
}
}
public static void release() {
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
}
好了皮假,上面就是關(guān)于實(shí)現(xiàn)的具體介紹了,如果想要詳細(xì)了解和查看功能效果請(qǐng)點(diǎn)傳送門(mén)