Android 开发如何显示 GIF 动画 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
alunbar
V2EX    Android

Android 开发如何显示 GIF 动画

  •  1
     
  •   alunbar 2015-10-23 11:39:18 +08:00 12918 次点击
    这是一个创建于 3644 天前的主题,其中的信息可能已经有所发展或是发生改变。
    gif 图动画在 android 中还是比较常用的,比如像新浪微博中,有很多 gif 图片,而且展示非常好,所以我也想弄一个。经过我多方的搜索资料和整理,终于弄出来了,其实 github 上有很多开源的 gif 的展示代码,我下载过几个,但是都不是很理想,不是我完全想要的。所以有时候就得自己学会总结,把开源的东西整理成自己的,现在无聊,也正好有朋友需要,所以现在整理了一下,留着以后备用!

    在这里主要用的是: android 中的 android.graphics.Movie 这个类,这是 android 提供给我们的一个非常便的工具。

    首先,重写控件 View,自定义一个展示 gif 图的 GifView ,代码如下:

    package net.loonggg.gif.view;
    import net.loonggg.gif.R;
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Movie;
    import android.os.Build;
    import android.util.AttributeSet;
    import android.view.View;
    public class GifView extends View {
    /**
    * 默认为 1 秒
    */
    private static final int DEFAULT_MOVIE_DURATION = 1000;
    private int mMovieResourceId;
    private Movie mMovie;
    private long mMovieStart;
    private int mCurrentAnimatiOnTime= 0;
    private float mLeft;
    private float mTop;
    private float mScale;
    private int mMeasuredMovieWidth;
    private int mMeasuredMovieHeight;
    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, R.styleable.CustomTheme_gifViewStyle);
    }
    public GifView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setViewAttributes(context, attrs, 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);
    }
    // 从描述文件中读出 gif 的值,创建出 Movie 实例
    final TypedArray array = context.obtainStyledAttributes(attrs,
    R.styleable.GifView, defStyle, R.style.Widget_GifView);
    mMovieResourceId = array.getResourceId(R.styleable.GifView_gif, -1);
    mPaused = array.getBoolean(R.styleable.GifView_paused, false);
    array.recycle();
    if (mMovieResourceId != -1) {
    mMovie = Movie.decodeStream(getResources().openRawResource(
    mMovieResourceId));
    }
    }
    /**
    * 设置 gif 图资源
    *
    * @param movieResId
    */
    public void setMovieResource(int movieResId) {
    this.mMovieResourceId = movieResId;
    mMovie = Movie.decodeStream(getResources().openRawResource(
    mMovieResourceId));
    requestLayout();
    }
    public void setMovie(Movie movie) {
    this.mMovie = movie;
    requestLayout();
    }
    public Movie getMovie() {
    return mMovie;
    }
    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);
    }
    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();
    }
    }
    Movie 其实管理着 GIF 动画中的多个帧,只需要通过 setTime() 一下就可以让它在 draw()的时候绘出相应的那帧图像。通过当前时间与 duration 之间的换算关系,是很容易实现 GIF 动起来的效果。

    其次,在 xml 布局文件中,把这个 view 定义进去,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <net.loonggg.gif.view.GifView
    android:id="@+id/gif1"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_gravity="center_horizontal"
    android:enabled="false" />
    <net.loonggg.gif.view.GifView
    android:id="@+id/gif2"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_gravity="center_horizontal"
    android:enabled="false" />
    </LinearLayout>
    最后,在 MainActivity 中的使用,代码如下:

    package net.loonggg.gif;
    import net.loonggg.gif.view.GifView;
    import android.app.Activity;
    import android.os.Bundle;
    public class Gif extends Activity {
    private GifView gif1, gif2;
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    gif1 = (GifView) findViewById(R.id.gif1);
    // 设置背景 gif 图片资源
    gif1.setMovieResource(R.raw.kitty);
    gif2 = (GifView) findViewById(R.id.gif2);
    gif2.setMovieResource(R.raw.b);
    // 设置暂停
    // gif2.setPaused(true);
    }
    }
    注意:与 ImageView 和其他 View 唯一的区别在于我加了一个 gif 属性。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <declare-styleable name="GifView">
    <attr name="gif" format="reference" />
    <attr name="paused" format="boolean" />
    </declare-styleable>
    <declare-styleable name="CustomTheme">
    <attr name="gifViewStyle" format="reference" />
    </declare-styleable>
    </resources>
    这个代码已经非常好了,使用也非常方便,其实不懂代码是什么意思也可以很好的用,只需要懂得我写注释的那几行和 Activity 里面的那几行代码就可以了!

    本文到此结束,需要的朋友可以参考下。

    向大家推荐一个移动开发的网站: www.mobile-open.com
    7 条回复    2015-10-23 17:35:24 +08:00
    lilyswf
        1
    lilyswf  
       2015-10-23 11:57:48 +08:00
    我比较好奇像美团那种加载动画是 gif 吗?还有一些根据下拉位置移动的加载动画,那个是 gif 吗?
    qiayue
        2
    qiayue  
    PRO
       2015-10-23 12:00:47 +08:00
    请问安卓开发中要在文章中显示自定义表情,一般是怎么做呢?
    neo2015
        3
    neo2015  
       2015-10-23 12:48:43 +08:00
    fresco 效果挺好的
    janstk
        4
    janstk  
       2015-10-23 12:51:38 +08:00
    @lilyswf 那是属性动画。
    kaedea
        5
    kaedea  
       2015-10-23 13:00:24 +08:00
    ……这都几百年前的技术了 还用 Moive^
    WayneWangWM
        6
    WayneWangWM  
       2015-10-23 15:48:54 +08:00
    Glide 可以加载 GIf
    liu37130
        7
    liu37130  
       2015-10-23 17:35:24 +08:00
    @lilyswf 感觉像 DrawableAnimation
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2964 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 20ms UTC 14:09 PVG 22:09 LAX 07:09 JFK 10:09
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86