GIF是一種很常見的動態圖片格式,在Android中它的使用場景很是多,大到啓動頁動畫、小到一個Loading展現,均可以用GIF動畫來完成,使用也很方便,直接從美工那邊拿過來用就成。若是項目趕時間或者自定義原生動畫太麻煩,GIF都是一個很好的選擇,相比於最新的WEBP格式的動畫,也有更好的兼容性(畢竟已經出現不少年了)。android
關於圖片加載我一直用的是Google推薦的Glide,圖片加載和緩存都作的很好,一樣也支持GIF動畫。不過Glide默認就是循環播放Gif,沒有開放相關的接口來控制Gif。這就使的咱們不能很好地控制Gif的播放,好比控制播放開始時間、播放次數,播放暫停、播放開始、結束事件的監聽,雖然用Glide可能作到(網上說能夠,但我沒找到方法),但操做也會很麻煩。git
除了第三方的庫,Android自帶的類android.graphics.Movie
也能夠用來加載播放Gif動畫,並且實現起來很簡單。按數據來源分別能夠從Gif文件的輸入流,文件路徑,字節數組中獲得Movie的實列。而後咱們能夠經過操做Movie對象來操做Gif文件。github
Movie decodeStream(InputStream is)canvas
Movie decodeFile(String pathName)數組
Movie decodeByteArray(byte[] data, int offset,int length)緩存
下面介紹下幾個movie的重要方法:bash
int width()
movie的寬,值等於gif圖片的寬,單位:px。int height()
movie的高,值等於gif圖片的高,單位:px。int duration()
movie播放一次的時長,也就是gif播放一次的時長,單位:毫秒。boolean isOpaque()
Gif圖片是否帶透明boolean setTime(int relativeMilliseconds)
設置movie當前處在什麼時間,而後找到對應時間的圖片幀,範圍0 ~ duration。返回是否成功找到那一幀。draw(Canvas canvas, float , float y)
draw(Canvas canvas, float x, float y, Paint paint)
在Canves中畫出當前幀對應的圖像。x,y對應Movie左上角在Canves中的座標。
以上就是Movie日常會用到大部分方法,下面就利用這些自定義VIew實現播放Gif動畫。ide
首先定義一些須要的屬性,用於在佈局文件中設置gif佈局
<declare-styleable name="GIFVIEW">
<!--gif文件引用-->
<attr name="gifSrc" format="reference" />
<!--是否加載完自動播放-->
<attr name="authPlay" format="boolean" />
<!--播放次放,默認永遠播放-->
<attr name="playCount" format="integer" />
</declare-styleable>複製代碼
而後定義Gif的播放監聽器,來監聽各個時段的事件,命名的含義都很簡單就再也不介紹了:動畫
public interface OnPlayListener {
void onPlayStart();
void onPlaying(int percent);
void onPlayPause(boolean pauseSuccess);
void onPlayRestart();
void onPlayEnd();
}複製代碼
聲明類,直接繼承ImageView,這樣咱們不只能夠顯示Gif動畫,也能夠顯示普通圖片: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文件,嘗試轉換爲bitmap顯示
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), movieResourceId);
if (bitmap != null) {
setImageBitmap(bitmap);
return;
}
}
movieDuration = movie.duration() == 0 ? DEFAULT_DURATION : movie.duration();
requestLayout();
}複製代碼
調用requestLayout從新計算View大小,並從新繪製。若是是gif格式則View寬高等於movie的寬高,不是則調用父類的測量方法。
@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);
}
}複製代碼
開始播放,其實就是調用invalidate從而調用onDraw方法就行UI繪製:
public void play(int counts) {
this.counts = counts;
reset();
if (mOnPlayListener != null) {
mOnPlayListener.onPlayStart();
}
invalidate();
}複製代碼
不斷調用onDraw方法來繪製Gif當前時間的圖片幀(一樣須要判斷是不是gif格式,不是則調用父類的繪製方法):
@Override
protected void onDraw(Canvas canvas) {
if (movie != null) {
if (!mPaused && hasStart) {
drawMovieFrame(canvas);
invalidateView();
} else {
drawMovieFrame(canvas);
}
} else {
super.onDraw(canvas);
}
}
/**
* 畫出gif幀
*/
private void drawMovieFrame(Canvas canvas) {
movie.setTime(getCurrentFrameTime());
movie.draw(canvas, 0.0f, 0.0f);
}複製代碼
最核心的方法就是計算當前時間須要繪製處於movie中的對應時間的圖片幀。
private int getCurrentFrameTime() {
if (movieDuration == 0)
return 0;
//由於有暫停,因此須要減去暫停時間
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);
}
}
}複製代碼
繼續Gif播放:
if (mPaused && mMoviePauseTime > 0) {
mPaused = false;
dealyTime = dealyTime + SystemClock.uptimeMillis() - mMoviePauseTime;
invalidate();
if (mOnPlayListener != null) {
mOnPlayListener.onPlayRestart();
}
}複製代碼
通過這些處理,咱們就能更好地控制Gif的播放流程了。下面簡單看下成品圖:
相信看了上面GifImageView的實現原理後,倒敘播放的實現也是很容易的。
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());
}複製代碼
以下圖,狗子的頭已經從原來的左邊轉到右邊變成了如今的右邊轉到左邊(ಠᴗಠ)。
這部分是我在寫完GifView後想到的一點進階功能,既然咱們已經實現了播放和暫停,即能控制在某個時間點播放指定的Gif圖片幀,若是再加入進度條,快進等功能,那麼不就能作到和視頻播放器同樣的功能了嗎?限於篇幅,我只簡單實現了進度條功能,更多功能實現請移步Github,地址:GifView。