您的位置:首页 > 移动开发 > Android开发

android 显示GIF动画

2016-09-07 12:01 393 查看
因为公司项目的需要,需要从后台获取gif动画,然后展示。Android本身并没有提供相关的直接显示gif动画的GifView之类的,通常使用webview显示,然后去搜索了下,发现有另外的实现思路,然后就借鉴了下。在这里总结下。另外,自己也动手写了一个gif加载框架,GifLoader,后面会开一篇讲解gifloader使用以及框架思路,已上传至jcenter 有需要的同学点这里

显示gif主要用到了android.graphics.Movie这个类



可以看到,movie提供了三个decode方法,分别从byte[],file,InputStream中获取数据,并转换为movie对象,实际上movie对象中的数据即是要展示的gif数据,看看每个方法的具体含义

draw(Canvas canvas, float x, float y)  //canvas 绘制的画布,x,绘制左边起始位置,y,绘制的头起始部位置

draw(Canvas canvas, float x, float y, Paint paint) //同上,多了一个paint,自定义画笔

duration() //gif的动画时长

height() //gif高度,这里的高度是指,gif图片内容本身的高度,非view的高度

isOpaque() //是否透明

setTime(int relativeMilliseconds) //设置当前的时间,可以控制当前显示哪一帧

width()  // 同height()


那怎么使用呢?我们上GifView源码

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.os.Build;

import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

public class GifView extends ImageView {
private static final String TAG = "ImageView";
private static final int DEFAULT_MOVIEW_DURATION = 2000;
private int mMovieResourceId;
private Movie mMovie;
private long mMovieStart;
private int mCurrentAnimationTime;
/**
* Position for drawing animation frames in the center of the view.
*/
private float mLeft;
private float mTop;

private int mMeasureWidth;
private int mMeasureHeight;

/**
* Scaling factor to fit the animation within view bounds.
*/
private float mScale = 1;

private volatile boolean mPaused;
private boolean mVisible = true;

public GifView(Context context) {
this(context, null);
}

public GifView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public GifView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setViewAttributes();
}

@SuppressLint("NewApi")
public void setViewAttributes() {

/**
* Starting from HONEYCOMB(Api Level:11) have to turn off HW acceleration to draw
* Movie on Canvas.
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMovie != null) {
if (!mPaused) {
updateAnimationTime();
drawMovieFrame(canvas);
invalidateView();
} else {
drawMovieFrame(canvas);
}
}
}

/**
* Draw current GIF frame
*/
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();
}

/**
* Calculate current animation time
*/
private void updateAnimationTime() {
long now = android.os.SystemClock.uptimeMillis();

if (mMovieStart == 0) {
mMovieStart = now;
}

int dur = mMovie.duration();

if (dur == 0) {
dur = DEFAULT_MOVIEW_DURATION;
}

mCurrentAnimationTime = (int) ((now - mMovieStart) % dur);
}

/**
* Invalidates view only if it is visible.
* <br>
* {@link #postInvalidateOnAnimation()} is used for Jelly Bean and higher.
*/
@SuppressLint("NewApi")
private void invalidateView() {
if (mVisible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postInvalidateOnAnimation
4000
();
} else {
invalidate();
}
}
}

@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();
}

public void setMovieResource(int movieResId) {
this.mMovieResourceId = movieResId;
mMovie = Movie.decodeStream(getResources().openRawResource(mMovieResourceId));
requestLayout();
}

public void setMovie(Movie movie) {
this.mMovie = movie;
setScale();
invalidate();
}

private void setScale() {
if (mMovie != null) {
float scaleW = (float) getWidth() / mMovie.width();
float scaleH = (float) getHeight() / mMovie.height();
mScale = Math.min(scaleH, scaleW);
}
}

public void setMovieTime(int time) {
mCurrentAnimationTime = time;
invalidate();
}

public void setPaused(boolean paused) {
this.mPaused = paused;

/**
* Calculate new movie start time, so that it resumes from the same
* frame.
*/
if (!paused) {
mMovieStart = android.os.SystemClock.uptimeMillis() - mCurrentAnimationTime;
}

invalidate();
}

public boolean isPaused() {
return this.mPaused;
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
if (mMovie != null) {
float scaleW = (float) getWidth() / mMovie.width();
float scaleH = (float) getHeight() / mMovie.height();
mScale = Math.min(scaleH, scaleW);
}
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mMovie != null) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
Log.e(TAG, "---moviewidth before" + mMovie.width());
int movieWidth = mMovie.width();
mMeasureWidth = Math.min(movieWidth, widthSize);
Log.e(TAG, "---moviewidth after" + mMovie.width());
} else {
mMeasureWidth = widthSize;
}
if (heightMode == MeasureSpec.AT_MOST) {
Log.e(TAG, "---height " + mMovie.height());
int movieHieght = mMovie.height();
mMeasureHeight = Math.min(movieHieght, heightSize);

} else {
mMeasureHeight = heightSize;
}
Log.e(TAG, "--wi : " + mMeasureWidth + "  h  : " + mMeasureHeight);
setMeasuredDimension(mMeasureWidth, mMeasureHeight);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}


代码还是比较简洁清晰的

继承自ImageView是因为加载gif是一个比较耗时的工作(从网上下载),这个时候可以设置一张过渡图片,用户体验较好。

onMeasure()的作用是测量view大小,当设置wrap_content时,取movieSize和viewSize较小值

onLayout里面的代码是为了计算mScale,即gif内容部分需要放大或者缩小的比例,如果不计算则会出现,内容和控件尺寸不匹配的情况,ondraw则根据动画时长,和当前时间,计算出当前显示的帧,类似于动画差值器,然后去绘制当前显示的画面。

使用也比较简单,直接调用几个set方法,将movie,或者gif资源传给gifview即可。

最后上效果



效果还是挺流畅的,喜欢的同学可以拿去

另外也开源了gifloader加载网络gif gifloader框架 需要的同学可以在项目中使用,后面会专门介绍gifloader的使用以及框架的解析

欢迎拍砖!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android gif