开题:加载GIF的场景在安卓开发中还比较常见,网上也有一些三方法的框架会支持对gif的加载,在上篇为大家推荐的图片加载库Glide也支持gif的加载Glide工具类的简单封装,今天给大家分享通过自定义View的方式加载GIf,通过此方式,避免了在java代码中对资源图片的操作,降低了代码的关注度,只需要把被加载的Gif资源文件在xml中声明即可。
我采用的实现方式是Android自带的类 android.graphics.Movie 来加载播放Gif动画也就是把Gif资源当成Movie来处理,按来源分别可以从Gif文件的输入流,文件路径,字节数组中得到Movie的实列,然后我们可以通过操作Movie对象来操作Gif文件。
一、实现分析:
1.以Movie的形式引入Gif资源文件
引入Movie资源的方法签名:
1.Movie decodeStream(InputStream is)
2.Movie decodeFile(String pathName)
3.Movie decodeByteArray(byte[] data, int offset,int length)
1.)我使用的是字节流的方式读入声明在xml引入的gif资源:操作如下
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GifView);
mGifId = array.getResourceId(R.styleable.GifView_gif, -1);
mPaused = array.getBoolean(R.styleable.GifView_paused, false);
array.recycle();
if (mGifId != -1) {
byte[] bytes = getGiftBytes();
mMovie = Movie.decodeByteArray(bytes, 0, bytes.length);
}
2.) 以java代码的方式引入
/**
* 设置gif图资源
*
* @param giftResId
*/
public void setMovieResource(int giftResId) {
this.mGifId = giftResId;
byte[] bytes = getGiftBytes();
mMovie = Movie.decodeByteArray(bytes, 0, bytes.length);
requestLayout();
}
上述两种方式中把Gif转成byte的方法如下:
/**
* 将gif图片转换成byte[]
*
* @return byte[]
*/
private byte[] getGiftBytes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = getResources().openRawResource(mGifId);
byte[] b = new byte[1024];
int len;
try {
while ((len = is.read(b, 0, 1024)) != -1) {
baos.write(b, 0, len);
}
baos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return baos.toByteArray();
}
2.下面就是通过onDraw方法中不断的去通知更新Movie的当前帧,然后重绘界面
@Override
protected void onDraw(Canvas canvas) {
if (mMovie != null) {
if (!mPaused) {
updateAnimationTime();
drawMovieFrame(canvas);
invalidateView();
} else {
drawMovieFrame(canvas);
}
}
}
附:这里简单提下,movie对象给我们封装了一个int 类型的duration()方法,调用该方法我们可以获得动画的总时长,
/**
* 更新当前显示进度
*/
private void updateAnimationTime() {
long now = android.os.SystemClock.uptimeMillis();
// 如果第一帧,记录起始时间
if (mMovieStart == 0) {
mMovieStart = now;
}
// 取出动画的时长
int dur = mMovie.duration();
if (dur == 0) {
dur = DEFAULT_MOVIE_DURATION;
}
// 算出需要显示第几帧
mCurrentAnimationTime = (int) ((now - mMovieStart) % dur);
}
3.总结:通过以上核心实现分析,我们大概明白其实利用Movie加载GIf的流程为:
引入Gif资源===》把gif转成byte===》利用Movie对象提供的方法计算出gif的时长===》通过onDraw不断的把更新之后的当前帧绘制出来。
二、完整实例
1.java代码部分
/**
* desc:利用Movie加载gif
* author:xiedong
* date:2017/11/21
*/
public class GifView extends View {
/**
* gif动态效果总时长,在未设置时长时默认为1秒
*/
private static final int DEFAULT_MOVIE_DURATION = 1000;
/**
* gif图片资源ID
*/
private int mGifId;
/**
* Movie实例,用来显示gift图片
*/
private Movie mMovie;
/**
* 显示gift图片的动态效果的开始时间
*/
private long mMovieStart;
/**
* 动态图当前显示第几帧
*/
private int mCurrentAnimationTime = 0;
/**
* 图片离屏幕左边的距离
*/
private float mLeft;
/**
* 图片离屏幕上边的距离
*/
private float mTop;
/**
* 图片的缩放比例
*/
private float mScale;
/**
* 图片在屏幕上显示的宽度
*/
private int mMeasuredMovieWidth;
/**
* 图片在屏幕上显示的高度
*/
private int mMeasuredMovieHeight;
/**
* 是否显示动画,为true表示显示,false表示不显示
*/
private boolean mVisible = true;
/**
* 动画效果是否被暂停
*/
private volatile boolean mPaused = false;
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);
setViewAttributes(context, attrs, defStyleAttr);
}
/**
* @param context 上下文
* @param attrs 自定义属性
* @param defStyle 默认风格
*/
@SuppressLint("NewApi")
private void setViewAttributes(Context context, AttributeSet attrs,
int defStyle) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GifView);
mGifId = array.getResourceId(R.styleable.GifView_gif, -1);
mPaused = array.getBoolean(R.styleable.GifView_paused, false);
array.recycle();
if (mGifId != -1) {
byte[] bytes = getGiftBytes();
mMovie = Movie.decodeByteArray(bytes, 0, bytes.length);
}
}
/**
* 设置gif图资源
*
* @param giftResId
*/
public void setMovieResource(int giftResId) {
this.mGifId = giftResId;
byte[] bytes = getGiftBytes();
mMovie = Movie.decodeByteArray(bytes, 0, bytes.length);
requestLayout();
}
/**
* 手动设置 Movie对象
*
* @param movie Movie
*/
public void setMovie(Movie movie) {
this.mMovie = movie;
requestLayout();
}
/**
* 得到Movie对象
*
* @return Movie
*/
public Movie getMovie() {
return mMovie;
}
/**
* 设置要显示第几帧动画
*
* @param time
*/
public void setMovieTime(int time) {
mCurrentAnimationTime = time;
invalidate();
}
/**
* 设置暂停
*
* @param paused
*/
public void setPaused(boolean paused) {
this.mPaused = paused;
if (!paused) {
mMovieStart = android.os.SystemClock.uptimeMillis()
- mCurrentAnimationTime;
}
invalidate();
}
/**
* 判断gif图是否停止了
*
* @return
*/
public boolean isPaused() {
return this.mPaused;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMovie != null) {
int movieWidth = mMovie.width();
int movieHeight = mMovie.height();
int maximumWidth = MeasureSpec.getSize(widthMeasureSpec);
float scaleW = (float) movieWidth / (float) maximumWidth;
mScale = 1f / scaleW;
mMeasuredMovieWidth = maximumWidth;
mMeasuredMovieHeight = (int) (movieHeight * mScale);
setMeasuredDimension(mMeasuredMovieWidth, mMeasuredMovieHeight);
} else {
setMeasuredDimension(getSuggestedMinimumWidth(),
getSuggestedMinimumHeight());
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mLeft = (getWidth() - mMeasuredMovieWidth) / 2f;
mTop = (getHeight() - mMeasuredMovieHeight) / 2f;
mVisible = getVisibility() == View.VISIBLE;
}
@Override
protected void onDraw(Canvas canvas) {
if (mMovie != null) {
if (!mPaused) {
updateAnimationTime();
drawMovieFrame(canvas);
invalidateView();
} else {
drawMovieFrame(canvas);
}
}
}
/**
* 重绘
*/
@SuppressLint("NewApi")
private void invalidateView() {
if (mVisible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postInvalidateOnAnimation();
} else {
invalidate();
}
}
}
/**
* 更新当前显示进度
*/
private void updateAnimationTime() {
long now = android.os.SystemClock.uptimeMillis();
// 如果第一帧,记录起始时间
if (mMovieStart == 0) {
mMovieStart = now;
}
// 取出动画的时长
int dur = mMovie.duration();
if (dur == 0) {
dur = DEFAULT_MOVIE_DURATION;
}
// 算出需要显示第几帧
mCurrentAnimationTime = (int) ((now - mMovieStart) % dur);
}
/**
* 绘制图片
*
* @param canvas 画布
*/
private void drawMovieFrame(Canvas canvas) {
// 设置要显示的帧,绘制即可
mMovie.setTime(mCurrentAnimationTime);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScale, mScale);
mMovie.draw(canvas, mLeft / mScale, mTop / mScale);
canvas.restore();
}
@SuppressLint("NewApi")
@Override
public void onScreenStateChanged(int screenState) {
super.onScreenStateChanged(screenState);
mVisible = screenState == SCREEN_STATE_ON;
invalidateView();
}
@SuppressLint("NewApi")
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mVisible = visibility == View.VISIBLE;
invalidateView();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mVisible = visibility == View.VISIBLE;
invalidateView();
}
/**
* 将gif图片转换成byte[]
*
* @return byte[]
*/
private byte[] getGiftBytes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = getResources().openRawResource(mGifId);
byte[] b = new byte[1024];
int len;
try {
while ((len = is.read(b, 0, 1024)) != -1) {
baos.write(b, 0, len);
}
baos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return baos.toByteArray();
}
}
2.xml中声明使用:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zhuandian.gif.MainActivity">
<com.zhuandian.gifview.GifView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:gif="@mipmap/leaf" />
</android.support.constraint.ConstraintLayout>