因为公司项目的需要,需要从后台获取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.     * 
* {@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(); } 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的使用以及框架的解析
欢迎拍砖!

更多相关文章

  1. Android(安卓)API Guides---Advanced RenderScript
  2. Android(安卓)逐帧动画
  3. android listview长按,单击各种事件捕捉
  4. 功能强大的android相机框架
  5. Android逐帧动画
  6. android 自定义菜单
  7. Android(安卓)属性动画使用(三)
  8. Android(安卓)插件框架 xCombine
  9. Android动画浅析

随机推荐

  1. Android 自动朗读(TTS)
  2. Android中使用 SharedPreferences
  3. 一封邮件引发的思考
  4. Android之线程阻塞(一)
  5. 《Android移动网站开发详解》
  6. 一年没在这机子上写android程序,竟然签名
  7. android的ImageView中XML属性src和backgr
  8. Android Sensor Development
  9. Android(安卓)R Variable Refresh Rate
  10. 拖动ListView时背景出现黑色的解决办法!