前言

Gif动画图在Android开发中很常见,但是Android自带的ImageView控件并不支持Gif动画效果,直接将Gif图设置到ImageView只会展示其中的一帧静态图。本文将探讨常用的展示Gif动态图的方法。

Movie播放

android.graphics.Movie对象能够将gif图播放出来,所以可以自定义GifImageView控件并在它的onDraw方法中使用Movie将gif每个时间点的帧画到Canvas上,之后在不停的更新GifImageView,每次画不同时间点的帧到Canvas上就实现了动态播放效果。

必须关闭所在的Activity的硬件加速功能,使用android:hardwareAccelerated=”false”配置Activity,否则无法看到动态播放效果。

实现代码如下,首先是自定义的GifImageView:

public class GifImageView extends AppCompatImageView {
    private static final String TAG = "GifImageView";
    private int mGifUrl = 0; // Gif图所在的路径
    private Movie mMovie; // 播放Gif图的movie独享
    private int duration = 0; // Gif图持续时间
    private long start = -1; // Gif图开始播放时间,初始化为-1
    public GifImageView(Context context) {
        this(context, null);
    }

    public GifImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GifImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if (attrs != null) {
            // 通过自定义属性的方式获取gif图的路径
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GifImageView);
            mGifUrl = typedArray.getResourceId(R.styleable.GifImageView_gif_url, 0);
            typedArray.recycle();
        }

        if (mGifUrl != 0) {
            String imageType = null;
            try {
                // 根据文件内容前4个字节判断文件是否是Gif图
                imageType = BitmapUtils.getImageType(context.getResources().openRawResource(mGifUrl));
                if ("gif".equalsIgnoreCase(imageType)) {
                    // 将Gif图加载到内存当中
                    byte[] array = streamToBytes(context.getResources().openRawResource(mGifUrl));
                    Log.d(TAG, "array.length = " + array.length);
                    // 将Gif图加载到movie对象里
                    mMovie = Movie.decodeByteArray(array, 0, array.length);
                    // 获取动画效果时长
                    duration = mMovie.duration();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static byte[] streamToBytes(InputStream is) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = is.read(buffer)) >= 0) {
                os.write(buffer, 0, len);
            }
        } catch (java.io.IOException e) {
        }
        return os.toByteArray();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.TRANSPARENT);
        super.onDraw(canvas);
        if (mMovie != null) {
            if (start == -1) {
                // 刚开始播放,初始化播放开始时间
                start = SystemClock.uptimeMillis();
            }

            // 根据当前时间和播放开始时间确定目前需要展示的帧
            int time = (int) ((SystemClock.uptimeMillis() - start) % duration);
            Log.d(TAG, "time = " + time + ", duration = " + duration);
            // movie指向展示的帧
            mMovie.setTime(time);
            // 画出当前帧到GifImageView视图上
            mMovie.draw(canvas, 0, 0);
            // 持续更新GifImageView视图,实现动态展示效果
            postInvalidateDelayed(10);
        }
    }
}

注意放置GifImageView的Activity关闭硬件加速功能。

<activity
    android:name=".MainActivity"
    android:hardwareAccelerated="false">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

WebView播放

WebView模仿的是浏览器功能,一般的浏览器都可以展示Gif动图,所以可以直接将Gif的url加载到WebView中展示。自定义的GifWebView如下所示:

gifWebView = (GifWebView) findViewById(R.id.gif_web_view);
gifWebView.loadUrl("file:///android_asset/hongbao.gif");

实现简单,而且不需要禁用硬件加速功能。

Glide播放

Glide框架提供了完美的Gif动图展示功能,它将Gif图包装成了GifDrawable然后设置到ImageView中,通常在真实项目中也使用这种方式。具体的实现原理还需要阅读Glide的源码,这里暂不深入介绍。

总结

Movie播放Gif实现比较原始,而且代码逻辑也比较复杂,选择的自动刷新时间也会影响Gif图的展示效果。WebView实现非常简单,但是很难控制Gif图与WebView大小一致,需要做缩放处理。Glide实现简单易用,但是需要整体依赖Glide库,不过对于大部分Android应用都需要图片加载框架,依赖Glide似乎不是个大问题。