安卓自定义View实现加载gif图片

来源:互联网 发布:java jstack 编辑:程序博客网 时间:2024/06/03 16:26

     开题:加载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>


最后附上我为大家抽成library的github引用传送门,方便大家直接以gradle的方式引入到项目中:安卓自定义View实现Gif加载

原创粉丝点击