Android自定義View播放Gif動(dòng)畫(huà)的示例
前言
GIF是一種很常見(jiàn)的動(dòng)態(tài)圖片格式,在Android中它的使用場(chǎng)景非常多,大到啟動(dòng)頁(yè)動(dòng)畫(huà)、小到一個(gè)Loading展示,都可以用GIF動(dòng)畫(huà)來(lái)完成,使用也很方便,直接從美工那邊拿過(guò)來(lái)用就成。如果項(xiàng)目趕時(shí)間或者自定義原生動(dòng)畫(huà)太麻煩,GIF都是一個(gè)很好的選擇,相比于最新的WEBP格式的動(dòng)畫(huà),也有更好的兼容性(畢竟已經(jīng)出現(xiàn)很多年了)。
關(guān)于圖片加載我一直用的是Google推薦的 Glide ,圖片加載和緩存都做的很好,同樣也支持GIF動(dòng)畫(huà)。不過(guò)Glide默認(rèn)就是循環(huán)播放Gif,沒(méi)有開(kāi)放相關(guān)的接口來(lái)控制Gif。這就使的我們不能很好地控制Gif的播放,比如控制播放開(kāi)始時(shí)間、播放次數(shù),播放暫停、播放開(kāi)始、結(jié)束事件的監(jiān)聽(tīng),雖然用Glide可能做到(網(wǎng)上說(shuō)可以,但我沒(méi)找到方法),但操作也會(huì)很麻煩。
分析
除了第三方的庫(kù),Android自帶的類 android.graphics.Movie 也可以用來(lái)加載播放Gif動(dòng)畫(huà),而且實(shí)現(xiàn)起來(lái)很簡(jiǎn)單。
- Movie decodeStream(InputStream is)
- Movie decodeFile(String pathName)
- Movie decodeByteArray(byte[] data, int offset,int length)
按來(lái)源分別可以從Gif文件的輸入流,文件路徑,字節(jié)數(shù)組中得到Movie的實(shí)列。然后我們可以通過(guò)操作Movie對(duì)象來(lái)操作Gif文件。
下面介紹下幾個(gè)方法:
int width() movie的寬,值等于gif圖片的寬,單位:px。
int height() movie的高,值等于gif圖片的高,單位:px。
int duration() movie播放一次的時(shí)長(zhǎng),也就是gif播放一次的時(shí)長(zhǎng),單位:毫秒。
boolean isOpaque() Gif圖片是否帶透明
boolean setTime(int relativeMilliseconds) 設(shè)置movie當(dāng)前處在什么時(shí)間,然后找到對(duì)應(yīng)時(shí)間的圖片幀,范圍0 ~ duration。返回是否成功找到那一幀。
draw(Canvas canvas, float , float y) draw(Canvas canvas, float x, float y, Paint paint)
在Canves中畫(huà)出當(dāng)前幀對(duì)應(yīng)的圖像。x,y對(duì)應(yīng)Movie左上角在Canves中的坐標(biāo)。
以上就是Movie平常會(huì)用到大部分方法,下面就利用這些自定義VIew實(shí)現(xiàn)播放Gif動(dòng)畫(huà)。
實(shí)現(xiàn)
首先定義一些需要的屬性,用于在布局文件中設(shè)置gif
<declare-styleable name="GIFVIEW">
<!--gif文件引用-->
<attr name="gifSrc" format="reference" />
<!--是否加載完自動(dòng)播放-->
<attr name="authPlay" format="boolean" />
<!--播放次放,默認(rèn)永遠(yuǎn)播放-->
<attr name="playCount" format="integer" />
</declare-styleable>
然后定義Gifde的播放監(jiān)聽(tīng)器,來(lái)監(jiān)聽(tīng)各個(gè)時(shí)段的事件,都很簡(jiǎn)單就不再介紹了:
public interface OnPlayListener {
void onPlayStart();
void onPlaying(int percent);
void onPlayPause(boolean pauseSuccess);
void onPlayRestart();
void onPlayEnd();
}
聲明類,直接繼承ImageView,這樣我們不僅可以顯示Gif動(dòng)畫(huà),也可以顯示普通圖片:
public class GifImageView extends AppCompatImageView
然后加載Gif圖片資源
public void setGifResource(int movieResourceId, OnPlayListener onPlayListener) {
mOnPlayListener = onPlayListener;
movie = Movie.decodeStream(getResources().openRawResource(movieResourceId));
if (movie == null) {
//如果movie為空,那么就不是gif文件,嘗試轉(zhuǎn)換為bitmap顯示
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), movieResourceId);
if (bitmap != null) {
setImageBitmap(bitmap);
return;
}
}
movieDuration = movie.duration() == 0 ? DEFAULT_DURATION : movie.duration();
requestLayout();
}
調(diào)用requestLayout重新計(jì)算View大小,并重新繪制。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (movie != null) {
int movieWidth = movie.width();
int movieHeight = movie.height();
setMeasuredDimension(movieWidth, movieHeight);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
開(kāi)始播放:
public void play(int counts) {
this.counts = counts;
reset();
if (mOnPlayListener != null) {
mOnPlayListener.onPlayStart();
}
invalidate();
}
不斷調(diào)用onDraw方法來(lái)繪制Gif當(dāng)前時(shí)間的圖片幀:
@Override
protected void onDraw(Canvas canvas) {
if (movie != null) {
if (!mPaused && hasStart) {
drawMovieFrame(canvas);
invalidateView();
} else {
drawMovieFrame(canvas);
}
} else {
super.onDraw(canvas);
}
}
/**
* 畫(huà)出gif幀
*/
private void drawMovieFrame(Canvas canvas) {
movie.setTime(getCurrentFrameTime());
movie.draw(canvas, 0.0f, 0.0f);
}
最核心的方法就是計(jì)算當(dāng)前時(shí)間需要播放處于movie中的哪個(gè)時(shí)間段。
private int getCurrentFrameTime() {
if (movieDuration == 0)
return 0;
//因?yàn)橛袝和#孕枰獪p去暫停時(shí)間
long now = SystemClock.uptimeMillis() - dealyTime;
int nowCount = (int) ((now - mMovieStart) / movieDuration);
if (counts != -1 && nowCount >= counts) {
hasStart = false;
if (mOnPlayListener != null) {
mOnPlayListener.onPlayEnd();
}
}
int currentTime = (int) ((now - mMovieStart) % movieDuration);
int percent = currentTime * 100 / movieDuration;
if (mOnPlayListener != null && hasStart) {
mOnPlayListener.onPlaying(percent);
}
return currentTime;
}
暫停Gif播放:
public void pause() {
if (movie != null && !mPaused && hasStart) {
mPaused = true;
invalidate();
mMoviePauseTime = SystemClock.uptimeMillis();
if (mOnPlayListener != null) {
mOnPlayListener.onPlayPause(true);
}
} else {
if (mOnPlayListener != null) {
mOnPlayListener.onPlayPause(false);
}
}
}
繼續(xù)Gif播放:
if (mPaused && mMoviePauseTime > 0) {
mPaused = false;
dealyTime = dealyTime + SystemClock.uptimeMillis() - mMoviePauseTime;
invalidate();
if (mOnPlayListener != null) {
mOnPlayListener.onPlayRestart();
}
}
經(jīng)過(guò)這些處理,我們就
能更好地控制Gif的播放流程了。下面簡(jiǎn)單看下成品圖:

進(jìn)階
倒敘播放
相信看了上面GifImageView的實(shí)現(xiàn)原理后,倒敘播放的實(shí)現(xiàn)也是很容易的。
public void playReserver() {
if (movie != null) {
reset();
reverse = true;
if (mOnPlayListener != null) {
mOnPlayListener.onPlayStart();
}
invalidate();
}
}
if (reverse) {
movie.setTime(movieDuration - getCurrentFrameTime());
} else {
movie.setTime(getCurrentFrameTime());
}
如下圖,狗子的頭已經(jīng)從原來(lái)的左邊轉(zhuǎn)到右邊變成了現(xiàn)在的右邊轉(zhuǎn)到左邊(ಠᴗಠ)。

像播放視頻一樣播放Gif動(dòng)畫(huà)
這部分是我在寫(xiě)完GifView后想到的一點(diǎn)進(jìn)階功能,既然我們已經(jīng)實(shí)現(xiàn)了播放和暫停,即能控制在某個(gè)時(shí)間點(diǎn)播放指定的Gif圖片幀,如果再加入進(jìn)度條,快進(jìn)等功能,那么不就能做到和視頻播放器一樣的功能了嗎?限于篇幅,我只簡(jiǎn)單實(shí)現(xiàn)了進(jìn)度條功能,更多功能實(shí)現(xiàn)請(qǐng)移步Github,地址: GifView 。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android自定義動(dòng)畫(huà)根據(jù)控件Y軸旋轉(zhuǎn)動(dòng)畫(huà)(仿紅包)
這篇文章主要介紹了Android自定義動(dòng)畫(huà)根據(jù)控件Y軸旋轉(zhuǎn)動(dòng)畫(huà)(仿紅包),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Android_RecyclerView實(shí)現(xiàn)上下滾動(dòng)廣告條實(shí)例(帶圖片)
本篇文章主要介紹了Android_RecyclerView實(shí)現(xiàn)上下滾動(dòng)廣告條實(shí)例(帶圖片),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android實(shí)現(xiàn)文件壓縮與解壓工具類
這篇文章主要為大家詳細(xì)介紹了如何使用Android實(shí)現(xiàn)一個(gè)文件壓縮與解壓工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04
Android編程雙重單選對(duì)話框布局實(shí)現(xiàn)與事件監(jiān)聽(tīng)方法示例
這篇文章主要介紹了Android編程雙重單選對(duì)話框布局實(shí)現(xiàn)與事件監(jiān)聽(tīng)方法,涉及Android雙重單選對(duì)話框的界面布局與事件監(jiān)聽(tīng)、響應(yīng)等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
Android判斷當(dāng)前應(yīng)用程序處于前臺(tái)還是后臺(tái)的兩種方法
這篇文章主要介紹了Android判斷當(dāng)前應(yīng)用程序處于前臺(tái)還是后臺(tái)的兩種方法,有需要的朋友可以參考一下2013-12-12
Android實(shí)現(xiàn)自定義輪播圖片控件示例
我們都知道我們做軟件的時(shí)候,有些應(yīng)用是有廣告的輪番圖的,我們實(shí)現(xiàn)這個(gè)功能的時(shí)候大多數(shù)是采用:ViewPager +LinearLayout來(lái)實(shí)現(xiàn)的,今天分享一下我自己自定義的廣告輪番圖的控件!2016-11-11
詳解Android應(yīng)用main函數(shù)的調(diào)用
Android常識(shí),App主線程初始化了Looper,調(diào)用prepare的地方是ActivityThread.main函數(shù)。問(wèn)題來(lái)了,App的main函數(shù)在哪兒調(diào)用,下面我們來(lái)一起學(xué)習(xí)一下吧2019-06-06

