LoginSignup
8
5

More than 1 year has passed since last update.

GIFアニメーションをImageViewで表示する

Last updated at Posted at 2020-03-20

はじめに

需要があるかはわからないですが、Gifアニメをこの度自プロジェクトでAndroidアプリ上で動かしたいという要望があったので調べました。

調べてみた

まずは、「Gif animation android imageview」などでググってみました。
すると・・・glideというライブラリを使うといいよ!
という記事がたくさん出てきました。

それ以外だと、Movieクラスを使ってCanvasに一枚ずつ書き出して・・・みたいなゴリ押しパターンがありました。
https://tomorrowkey-2.hatenadiary.org/entry/20100419/1271678022

弊社では、ライブラリを使うにはちょっと時間がかかるのと、外部の信用できる会社のものでないと結構たいへんなので、glideライブラリを使うというのは避けたかったので、ゴリ押しパターンを実装してみようということになりました。

準備

Gifアニメのダウンロード

とりあえず、フリー素材のGifアニメをダウンロード。
下記のサイトからもらいました。ありがとう。
https://sozai-good.com/illust/gifanimation/

assetsフォルダの作成

とりあえず表示するだけなので、assetsにダウンロードしたGifアニメを置くことに。

projectツリーのappあたりで右クリックして
[New]->[Folder]->[Assets Folder]をすると簡単につくれます。
image.png

assetsフォルダがapp->src->mainの下にできていたら、ダウンロードしたGifアニメを入れます。
Gifアニメのファイル名は適当に。
image.png

レイアウトファイル

今回は、Activityの画面にImageViewを配置するだけにします。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <ImageView
            android:id="@+id/image_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Gifアニメを表示させる

Gifアニメを表示する場合、Movieがversion28から非推奨になっているため、
コードをOSごとに分ける必要があります。

Android P以上の場合

リファレンス通りに、MovieはdeprecatedなのでAnimatedImageDrawable
Movieのリファレンス
AnimatedImageDrawableのリファレンス

AnimatedImageDrawableを、assetsからGifアニメを取得して表示します。

@RequiresApi(Build.VERSION_CODES.P)
    private fun getGifAnimationDrawable(): AnimatedImageDrawable {
        val source = ImageDecoder.createSource(assets, "gif_anim_toy_poodle.gif")
        return ImageDecoder.decodeDrawable(source) as? AnimatedImageDrawable
            ?: throw ClassCastException()
    }

手順は簡単です。
1. ImageDecoder.createSourceで、第一引数にassetManager、第二引数にassetsに入っているファイル名を指定してください。
ちなみに、このImageDecoder.createSourceは他にもFileを指定したり、res & resIDを指定したり、ContentResolver & URIなど、複数のファイル取得に対応していそうです。
2. 1で取得したImageDecoder.SourceインスタンスをImageDecoder.decodeDrawable()に渡してDrawableを取得します。
3. AnimatedImageDrawableにキャストします。

取得ができたら、ImageViewに設定します。

// AnimatedImageDrawableを取得する
val drawable = getGifAnimationDrawable() 

// アニメーションをセットする
binding.imageView.setImageDrawable(drawable)

// アニメーションを開始する
drawable.start()

Android P未満の場合

Android P未満の場合だと、ImageDecoderAnimatedImageDrawableが利用できません。
なので、従来の通りMovieクラスを使った方法を一応書いておきます。

参考にしたサイトはこちらで、基本的にこちらの写経です。
https://end0tknr.hateblo.jp/entry/20120714/1342247240

カスタムのDrawableを用意する

MovieクラスでのGifアニメーション起動の方法は、onDrawでMovieを動かす方法になるので、
カスタムクラスを作る必要があります。
参考にしたサイトではViewを継承していましたが、今回はImageViewに設定したかったので、Drawableを継承しました。

CustomAnimatedDrawable.kt
class CustomAnimatedDrawable(
    private val inputStream: InputStream
): Drawable() {
    private val _movie by lazy {
        Movie.decodeStream(inputStream)
    }

    private var _movieStart = 0
    private var _loop = true
    private var _stop = false
    private var relativeMillisecond = 0

    override fun draw(canvas: Canvas) {
        canvas.apply {
            drawColor(Color.TRANSPARENT)
            scale(width / _movie.width().toFloat(),
                height / _movie.height().toFloat() )
        }
        val now = SystemClock.uptimeMillis()

        if (_movieStart == 0) {
            _movieStart = now.toInt()
        }

        relativeMillisecond = when {
            _stop -> {
                _movieStart = 0
                relativeMillisecond
            }
            _loop -> ((now - _movieStart) % _movie.duration()).toInt()
            else -> (now - _movieStart).toInt()
        }

        _movie.apply {
            setTime(relativeMillisecond)
            draw(canvas, 0f, 0f)
        }

        invalidateSelf()
    }

    override fun setAlpha(alpha: Int) {
    }

    override fun getOpacity(): Int = PixelFormat.UNKNOWN

    override fun setColorFilter(colorFilter: ColorFilter?) {}

    fun stop() {
        _stop = true
    }

    fun start() {
        _stop = false
        _movieStart = 0
    }

    fun isRunning() = !_stop
}

assetsからファイルをopenする

あとはassetsからファイルを取得して、カスタムクラスにinputStreamを渡してあげればOKです。

    private fun getGifAnimationDrawableLessThanP(): CustomAnimatedDrawable {
        val inputStream = assets.open(ANIMATION_GIF_FILE_NAME)
        return CustomAnimatedDrawable(inputStream)
    }

これでOKです。

キャプチャ

recordで動画を撮ったんですが、いつからかわからないですが、.webm方式での保存になってしまい、かつadb shell screen record..みたいなのもうまく動かないので、泣く泣くキャプチャを取るしかできませんでした。
image.png
この子がペケペケ動きます。

コード

サンプルのコードはここにおいておきました。
なんかpushの仕方間違えちゃったので一階層無駄があります。
https://github.com/keikyukyun/gif_animation_sample

最後に

久しぶりにgitにもアップしたけど、WebViewなら一撃という噂も。

8
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5