android 显示GIF动画

来源:互联网 发布:万方数据库官网登录 编辑:程序博客网 时间:2024/06/07 07:48

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

0 0
原创粉丝点击