效果圖
GIFView效果圖
Android的ImageView是不支持GIF播放的额港,如果需要讓ImageView支持GIF就需要做自定義View移斩。主流圖片加載框架中,如果要加載GIF肠套,一般使用Glide猖任。
播放GIF
一般有兩種方法實現(xiàn)
- 簡單地使用Movie:存在一定的性能問題朱躺,適用于少數(shù)圖片
- 使用NDK對GIF進行解碼:性能較好,適用于列表類的GIF播放宇弛。android-gif-drawable
Movie類
public native int width(); // 獲取GIF圖片寬度
public native int height(); // 獲取GIF圖片高度
public native int duration(); // 獲取GIF圖片時長
public native boolean setTime(int time); // 設(shè)置當前GIF幀
public void draw(Canvas canvas, float x, float y, Paint paint); // 把當前幀畫到Canvas上
public void draw(Canvas canvas, float x, float y); // 把當前幀畫到Canvas上
// 三種解GIF圖的方式
public static Movie decodeStream(InputStream is);
public static native Movie decodeByteArray(byte[] bytes, int start, int length);
public static Movie decodeFile(String pathName);
該類的使用很簡單涯肩,通過setTime設(shè)置當前幀巢钓,然后不斷調(diào)用draw把當前幀畫出來就行了
GIFView設(shè)計
實現(xiàn)方法:通過自定義View症汹,每次onDraw的時候得到Canvas贷腕,更新當前幀把內(nèi)容滑到Canvas上
需要支持的功能:
- 播放GIF
- 循環(huán)播放
- 播放/暫停
- 尺寸控制(wrap_content/match_parent/指定尺寸)
- 縮放(FIT_START、FIT_CENTER破婆、FIT_END胸囱、CENTER、CENTER_INSIDE裳扯、CENTER_CROP谤职、FIT_XY七種縮放模式)
GIF解碼允蜈、播放/暫停、循環(huán)支持
private Movie mMovie;
private long mStartTime;
private long mPauseTime;
private boolean mIsLoop;
private boolean mIsStart;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMovie == null) {
return;
}
long now = SystemClock.uptimeMillis();
int currentTime = (int) (now - mStartTime);
if (currentTime >= mMovie.duration()) {
if (mIsLoop) {
mStartTime = SystemClock.uptimeMillis();
currentTime = 0;
mIsStart = true;
} else if (mIsStart) {
currentTime = mMovie.duration();
mIsStart = false;
}
}
mMovie.setTime(currentTime);
mMovie.draw(canvas, 0, 0);
if (mIsStart) {
postInvalidate();
}
}
public void pause() {
if (!mIsStart) {
return;
}
mIsStart = false;
// 記錄播放的位置
mPauseTime = SystemClock.uptimeMillis() - mStartTime;
postInvalidate();
}
public void resume() {
if (mIsStart) {
return;
}
mIsStart = true;
// 恢復(fù)到播放的相對位置
mStartTime = SystemClock.uptimeMillis() - mPauseTime;
postInvalidate();
}
public void setMovie(Movie movie) {
mMovie = movie;
mStartTime = SystemClock.uptimeMillis();
mIsStart = true;
postInvalidate();
}
public void setSource(int id) {
setSource(getResources().openRawResource(id));
}
public void setSource(byte[] bytes, int start, int len) {
setMovie(Movie.decodeByteArray(bytes, start, len));
}
public void setSource(InputStream inputStream) {
setMovie(Movie.decodeStream(inputStream));
}
public void setSource(String pathName) {
setMovie(Movie.decodeFile(pathName));
}
public void setLoop(boolean loop) {
mIsLoop = loop;
postInvalidate();
}
至此,最簡單地功能已經(jīng)實現(xiàn)了爆安,該GIFView已經(jīng)可以播放GIF圖片了仔引。
尺寸控制(wrap_content/match_parent/指定尺寸)
int width = 0;
int height = 0;
if (mMovie != null) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
if (wMode == MeasureSpec.EXACTLY) {
width = wSize;
} else {
width = mMovie.width();
}
if (hMode == MeasureSpec.EXACTLY) {
height = hSize;
} else {
height = mMovie.height();
}
}
setMeasuredDimension(width, height);
尺寸控制也簡單咖耘,指定寬高/match_parent就直接設(shè)置寬高儿倒,wrap_content就使用gif的寬高。
縮放
推薦先了解一下8種ScaleType分別是怎么縮放的彻犁。
縮放的話尺寸是不受印象的汞幢,其中主要設(shè)置的變量是繪制的定位點以及寬高伸縮
下面是各種縮放類型的定位點以及寬高縮放比例計算值通過代碼表示微谓。
private int mLeft;
private int mTop;
private float mScaleX;
private float mScaleY;
private void calcScale() {
if (mMovie == null) {
return;
}
float imageW = mMovie.width();
float imageH = mMovie.height();
float viewW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
float viewH = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
if (mScaleType == ImageView.ScaleType.FIT_XY) {
mScaleX = viewW / imageW;
mScaleY = viewH / imageH;
} else if (mScaleType == ImageView.ScaleType.FIT_START
|| mScaleType == ImageView.ScaleType.FIT_CENTER
|| mScaleType == ImageView.ScaleType.FIT_END) {
mScaleY = mScaleX = viewH / imageH;
} else if (mScaleType == ImageView.ScaleType.CENTER) {
mScaleX = mScaleY = 1;
} else if (mScaleType == ImageView.ScaleType.CENTER_CROP) {
mScaleX = viewW / imageW;
mScaleY = viewH / imageH;
mScaleX = mScaleY = Math.max(mScaleX, mScaleY);
} else if (mScaleType == ImageView.ScaleType.CENTER_INSIDE) {
mScaleX = viewW / imageW;
mScaleY = viewH / imageH;
mScaleX = mScaleY = Math.min(mScaleX, mScaleY);
}
}
private void calcLocation() {
if (mMovie == null) {
return;
}
int imageW = mMovie.width();
int imageH = mMovie.height();
int viewW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int viewH = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int left = getPaddingLeft();
int top = getPaddingTop();
if (mScaleType == ImageView.ScaleType.FIT_XY) {
mLeft = left;
mTop = top;
} else if (mScaleType == ImageView.ScaleType.FIT_START) {
mLeft = left;
mTop = top;
} else if (mScaleType == ImageView.ScaleType.FIT_CENTER) {
mLeft = (int) (left + (viewW - imageW * mScaleX) / 2);
mTop = top;
} else if (mScaleType == ImageView.ScaleType.FIT_END) {
mLeft = (int) (left + viewW - imageW * mScaleX);
mTop = top;
} else if (mScaleType == ImageView.ScaleType.CENTER) {
mLeft = -(imageW - viewW) / 2;
mTop = -(imageH - viewH) / 2;
} else if (mScaleType == ImageView.ScaleType.CENTER_CROP) {
mLeft = (int) -(Math.abs(viewW - imageW * mScaleX) / 2);
mTop = (int) -(Math.abs(viewH - imageH * mScaleY) / 2);
} else if (mScaleType == ImageView.ScaleType.CENTER_INSIDE) {
mLeft = (int) (left + (viewW - imageW * mScaleX) / 2);
mTop = (int) (top + (viewH - imageH * mScaleY) / 2);
}
}
計算得到對應(yīng)的值后仲智,只需要稍微修改onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMovie == null) {
return;
}
long now = SystemClock.uptimeMillis();
int currentTime = (int) (now - mStartTime);
if (currentTime >= mMovie.duration()) {
if (mIsLoop) {
mStartTime = SystemClock.uptimeMillis();
currentTime = 0;
mIsStart = true;
} else if (mIsStart) {
currentTime = mMovie.duration();
mIsStart = false;
}
}
mMovie.setTime(currentTime);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScaleX, mScaleY);
mMovie.draw(canvas, mLeft / mScaleX, mTop / mScaleY);
canvas.restore();
if (mIsStart) {
postInvalidate();
}
}
需要在適當?shù)臅r候?qū)Χㄎ稽c以及縮放值進行重新的計算
完整代碼
attrs.xml
<declare-styleable name="GIFView">
<attr name="view_gif_loop" format="boolean" />
<attr name="view_gif_source" format="reference" />
</declare-styleable>
GIFView
public class GIFView extends View {
private Movie mMovie;
private long mStartTime;
private long mPauseTime;
private boolean mIsLoop;
private boolean mIsStart;
private int mLeft;
private int mTop;
private float mScaleX;
private float mScaleY;
private ImageView.ScaleType mScaleType = ImageView.ScaleType.CENTER_CROP;
private Runnable mCalcRunnable = new Runnable() {
@Override
public void run() {
calcScale();
calcLocation();
}
};
public GIFView(Context context) {
this(context, null);
}
public GIFView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GIFView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
}
private void initAttrs(AttributeSet attrs) {
TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.GIFView);
mIsLoop = typedArray.getBoolean(R.styleable.GIFView_view_gif_loop, false);
int id = typedArray.getResourceId(R.styleable.GIFView_view_gif_source, -1);
if (id != -1) {
setSource(id);
}
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
if (mMovie != null) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
if (wMode == MeasureSpec.EXACTLY) {
width = wSize;
} else {
width = mMovie.width();
}
if (hMode == MeasureSpec.EXACTLY) {
height = hSize;
} else {
height = mMovie.height();
}
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMovie == null) {
return;
}
long now = SystemClock.uptimeMillis();
int currentTime = (int) (now - mStartTime);
if (currentTime >= mMovie.duration()) {
if (mIsLoop) {
mStartTime = SystemClock.uptimeMillis();
currentTime = 0;
mIsStart = true;
} else if (mIsStart) {
currentTime = mMovie.duration();
mIsStart = false;
}
}
mMovie.setTime(currentTime);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScaleX, mScaleY);
mMovie.draw(canvas, mLeft / mScaleX, mTop / mScaleY);
canvas.restore();
if (mIsStart) {
postInvalidate();
}
}
public void pause() {
if (!mIsStart) {
return;
}
mIsStart = false;
// 記錄播放的位置
mPauseTime = SystemClock.uptimeMillis() - mStartTime;
postInvalidate();
}
public void resume() {
if (mIsStart) {
return;
}
mIsStart = true;
// 恢復(fù)到播放的相對位置
mStartTime = SystemClock.uptimeMillis() - mPauseTime;
postInvalidate();
}
/**
* 獲取當前播放的幀
*
* @return
*/
public Bitmap getCurrentFrame() {
if (mMovie == null) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(mMovie.width(), mMovie.height(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.scale(mScaleX, mScaleY);
mMovie.draw(canvas, mLeft, mTop);
return bitmap;
}
public Movie getMovie() {
return mMovie;
}
public void setMovie(Movie movie) {
mMovie = movie;
mStartTime = SystemClock.uptimeMillis();
mIsStart = true;
if (getMeasuredHeight() == 0 || getMeasuredWidth() == 0) {
post(mCalcRunnable);
} else {
mCalcRunnable.run();
}
requestLayout();
postInvalidate();
}
public void setSource(int id) {
setSource(getResources().openRawResource(id));
}
public void setSource(byte[] bytes, int start, int len) {
setMovie(Movie.decodeByteArray(bytes, start, len));
}
public void setSource(InputStream inputStream) {
setMovie(Movie.decodeStream(inputStream));
}
public void setSource(String pathName) {
setMovie(Movie.decodeFile(pathName));
}
public void setLoop(boolean loop) {
mIsLoop = loop;
postInvalidate();
}
public void setScaleType(ImageView.ScaleType scaleType) {
if (scaleType == ImageView.ScaleType.MATRIX) {
throw new UnsupportedOperationException("不支持MATRIX類型縮放");
}
this.mScaleType = scaleType;
if (getMeasuredHeight() == 0 || getMeasuredWidth() == 0) {
post(mCalcRunnable);
} else {
mCalcRunnable.run();
}
}
private void calcScale() {
if (mMovie == null) {
return;
}
float imageW = mMovie.width();
float imageH = mMovie.height();
float viewW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
float viewH = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
if (mScaleType == ImageView.ScaleType.FIT_XY) {
mScaleX = viewW / imageW;
mScaleY = viewH / imageH;
} else if (mScaleType == ImageView.ScaleType.FIT_START
|| mScaleType == ImageView.ScaleType.FIT_CENTER
|| mScaleType == ImageView.ScaleType.FIT_END) {
mScaleY = mScaleX = viewH / imageH;
} else if (mScaleType == ImageView.ScaleType.CENTER) {
mScaleX = mScaleY = 1;
} else if (mScaleType == ImageView.ScaleType.CENTER_CROP) {
mScaleX = viewW / imageW;
mScaleY = viewH / imageH;
mScaleX = mScaleY = Math.max(mScaleX, mScaleY);
} else if (mScaleType == ImageView.ScaleType.CENTER_INSIDE) {
mScaleX = viewW / imageW;
mScaleY = viewH / imageH;
mScaleX = mScaleY = Math.min(mScaleX, mScaleY);
}
}
private void calcLocation() {
if (mMovie == null) {
return;
}
int imageW = mMovie.width();
int imageH = mMovie.height();
int viewW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int viewH = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int left = getPaddingLeft();
int top = getPaddingTop();
if (mScaleType == ImageView.ScaleType.FIT_XY) {
mLeft = left;
mTop = top;
} else if (mScaleType == ImageView.ScaleType.FIT_START) {
mLeft = left;
mTop = top;
} else if (mScaleType == ImageView.ScaleType.FIT_CENTER) {
mLeft = (int) (left + (viewW - imageW * mScaleX) / 2);
mTop = top;
} else if (mScaleType == ImageView.ScaleType.FIT_END) {
mLeft = (int) (left + viewW - imageW * mScaleX);
mTop = top;
} else if (mScaleType == ImageView.ScaleType.CENTER) {
mLeft = -(imageW - viewW) / 2;
mTop = -(imageH - viewH) / 2;
} else if (mScaleType == ImageView.ScaleType.CENTER_CROP) {
mLeft = (int) -(Math.abs(viewW - imageW * mScaleX) / 2);
mTop = (int) -(Math.abs(viewH - imageH * mScaleY) / 2);
} else if (mScaleType == ImageView.ScaleType.CENTER_INSIDE) {
mLeft = (int) (left + (viewW - imageW * mScaleX) / 2);
mTop = (int) (top + (viewH - imageH * mScaleY) / 2);
}
}
}