はじめに
需要があるかはわからないですが、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]をすると簡単につくれます。
assetsフォルダがapp->src->mainの下にできていたら、ダウンロードしたGifアニメを入れます。
Gifアニメのファイル名は適当に。
レイアウトファイル
今回は、Activityの画面にImageViewを配置するだけにします。
<?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()
}
手順は簡単です。
-
ImageDecoder.createSource
で、第一引数にassetManager、第二引数にassetsに入っているファイル名を指定してください。
ちなみに、このImageDecoder.createSource
は他にもFile
を指定したり、res & resID
を指定したり、ContentResolver & URI
など、複数のファイル取得に対応していそうです。 - 1で取得した
ImageDecoder.Source
インスタンスをImageDecoder.decodeDrawable()
に渡してDrawableを取得します。 -
AnimatedImageDrawable
にキャストします。
取得ができたら、ImageViewに設定します。
// AnimatedImageDrawableを取得する
val drawable = getGifAnimationDrawable()
// アニメーションをセットする
binding.imageView.setImageDrawable(drawable)
// アニメーションを開始する
drawable.start()
Android P未満の場合
Android P未満の場合だと、ImageDecoder
とAnimatedImageDrawable
が利用できません。
なので、従来の通りMovieクラスを使った方法を一応書いておきます。
参考にしたサイトはこちらで、基本的にこちらの写経です。
https://end0tknr.hateblo.jp/entry/20120714/1342247240
カスタムのDrawableを用意する
MovieクラスでのGifアニメーション起動の方法は、onDrawでMovieを動かす方法になるので、
カスタムクラスを作る必要があります。
参考にしたサイトではViewを継承していましたが、今回はImageViewに設定したかったので、Drawableを継承しました。
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..みたいなのもうまく動かないので、泣く泣くキャプチャを取るしかできませんでした。
この子がペケペケ動きます。
コード
サンプルのコードはここにおいておきました。
なんかpushの仕方間違えちゃったので一階層無駄があります。
https://github.com/keikyukyun/gif_animation_sample
最後に
久しぶりにgitにもアップしたけど、WebViewなら一撃という噂も。