5

I have a gif that I would like to place into my app. I know how to insert image resources, but when I try adding the gif it becomes a static image.

DrawImage(image = +imageResource(R.drawable.gif))

Has anyone tried adding a gif into Jetpack Compose, as struggling to find docs online as to how?

Michael Johnston
  • 577
  • 9
  • 23
  • `Quick question` it's usually these which are the hardest to answer :) – a_local_nobody Feb 14 '20 at 15:58
  • @a_local_nobody Annoyingly does seem to be the case a lot of the time :) – Michael Johnston Feb 14 '20 at 16:01
  • i haven't worked with compose myself (yet) but i have a feeling this _might_ be a very good question as it might not even be possible, hence my comment (and my upvote). this is all irrelevant conversation (someone will probably flag and remove this, as they should) but i hope you find an answer :) rare to see interesting questions these days, sadly – a_local_nobody Feb 14 '20 at 16:06
  • I've only just started using it myself. Experimenting with how it all works compared to the old way of doing things. If I figure out a solution I'll be sure to add it here (if its not already removed) – Michael Johnston Feb 14 '20 at 16:12
  • 1
    Do GIFs animate anywhere in the stock Android SDK `View` system? At least for the first several years of Android's existence, `ImageView` would not animate a GIF, for example. Developers wound up using `WebView` or `Movie` (IIRC) until [a bunch of animated-GIF-capable rendering libraries](https://android-arsenal.com/tag/193?sort=created) became available. Your question suggests that you expect animation, but is that a reasonable expectation? – CommonsWare Feb 14 '20 at 22:05

4 Answers4

3

Starting from coil 1.3.0 gif supported is added to Jetpack Compose's version of Coil. So you can use existing coil docs for supporting gif decoding.

TL;DR

Add the following libraries to gradle:

implementation("io.coil-kt:coil:2.0.0-rc02")
implementation("io.coil-kt:coil-gif:2.0.0-rc02")
implementation("io.coil-kt:coil-compose:2.0.0-rc02")

Gif set up code:

// Create an ImageLoader

val imgLoader = ImageLoader.invoke(context).newBuilder()
  .componentRegistry {
    if (SDK_INT >= 28) {
      add(ImageDecoderDecoder(context))
    } else {
      add(GifDecoder())
    }
  }.build()

// Use in Image

Image(
  painter = rememberImagePainter(data = R.drawable.YOURBESTGIF, imageLoader = imgLoader),
  ...
)
FireTr3e
  • 77
  • 7
kaustubhpatange
  • 322
  • 2
  • 11
2

I was able to display an animated GIF in Compose 0.1.0-dev16 using this code (taken from https://github.com/luca992/coil-composable/blob/master/coil-composable/src/androidMain/kotlin/com/luca992/compose/image/CoilImage.kt and modified):

import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.os.Build.VERSION.SDK_INT
import androidx.annotation.Px
import androidx.compose.foundation.Image
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.WithConstraints
import androidx.compose.ui.geometry.Size.Companion.Zero
import androidx.compose.ui.graphics.ImageAsset
import androidx.compose.ui.graphics.asImageAsset
import androidx.compose.ui.platform.ContextAmbient
import androidx.compose.ui.unit.Constraints.Companion.Infinity
import androidx.core.graphics.drawable.toBitmap
import androidx.ui.tooling.preview.Preview
import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.request.CachePolicy
import coil.request.LoadRequest
import coil.request.LoadRequestBuilder
import coil.size.Scale
import coil.target.Target
import kotlinx.coroutines.*


@Composable
fun CoilImage(
    model: Any,
    modifier : Modifier = Modifier,
    customize: LoadRequestBuilder.() -> Unit = {}
) {
    WithConstraints(modifier) {
        var width =
            if (constraints.maxWidth > Zero.width && constraints.maxWidth < Infinity) {
                constraints.maxWidth
            } else {
                -1
            }

        var height =
            if (constraints.maxHeight > Zero.height && constraints.maxHeight < Infinity) {
                constraints.maxHeight
            } else {
                -1
            }

        //if height xor width not able to be determined, make image a square of the determined dimension
        if (width == -1) width = height
        if (height == -1) height = width

        val image = state<ImageAsset> { ImageAsset(width,height) }
        val context = ContextAmbient.current
        var animationJob : Job? = remember { null }
        onCommit(model) {


            val target = object : Target {
                override fun onStart(placeholder: Drawable?) {
                    placeholder?.apply {
                        animationJob?.cancel()
                        if(height != -1 && width != -1) {
                            animationJob = image.update(this, width, height)
                        } else if (height == -1) {
                            val scaledHeight = intrinsicHeight * (width / intrinsicWidth )
                            animationJob = image.update(this, width, scaledHeight)
                        } else if (width == -1) {
                            val scaledWidth = intrinsicWidth * (height / intrinsicHeight)
                            animationJob = image.update(this, scaledWidth, height)
                        }
                    }
                }

                override fun onSuccess(result: Drawable) {
                    animationJob?.cancel()
                    animationJob = image.update(result)
                }

                override fun onError(error: Drawable?) {
                    error?.run {
                        animationJob?.cancel()
                        animationJob = image.update(error)
                    }
                }
            }



            val loader = ImageLoader.Builder(context)
                .componentRegistry {
                    if (SDK_INT >= 28) {
                        add(ImageDecoderDecoder())
                    } else {
                        add(GifDecoder())
                    }
                }.build()


            val request = LoadRequest.Builder(context)
                .data(model)
                .size(width, height)
                .scale(Scale.FILL)
                .diskCachePolicy(CachePolicy.ENABLED)
                .apply{customize(this)}
                .target(target)

            val requestDisposable = loader.execute(request.build())

            onDispose {
                image.value = ImageAsset(width,height)
                requestDisposable.dispose()
                animationJob?.cancel()
            }
        }
        Image(modifier = modifier, asset = image.value)
    }
}

internal fun MutableState<ImageAsset>.update(drawable: Drawable, @Px width: Int? = null, @Px height: Int? = null) : Job? {
    if (drawable is Animatable) {
        (drawable as Animatable).start()

        return GlobalScope.launch(Dispatchers.Default) {
            while (true) {
                val asset = drawable.toBitmap(
                    width = width ?: drawable.intrinsicWidth,
                    height =  height ?: drawable.intrinsicHeight)
                    .asImageAsset()
                withContext(Dispatchers.Main) {
                    value = asset
                }
                delay(16)
                //1000 ms / 60 fps = 16.666 ms/fps
                //TODO: figure out most efficient way to dispaly a gif
            }
        }
    } else {
        value = drawable.toBitmap(
            width = width ?: drawable.intrinsicWidth,
            height =  height ?: drawable.intrinsicHeight)
            .asImageAsset()
        return null
    }
}

This depends on Coil:

implementation 'io.coil-kt:coil:0.11.0'
implementation 'io.coil-kt:coil-gif:0.11.0'

Use as follows:

setContent {
  CoilImage("https://example.com/image.gif")
}
Daniels Šatcs
  • 477
  • 6
  • 18
1

You can use the accompanist - Utils library for all images loading composables

you can use it like this:

GlideImage(
        data = R.drawable.giphy,
        contentDescription = ""
    )
YotiS
  • 53
  • 6
0

This can easily be done using coil as following

@Composable
fun GifImage(
    modifier: Modifier = Modifier,
    imageID: Int
){
    val context = LocalContext.current
    val imageLoader = ImageLoader.Builder(context)
        .componentRegistry {
            if (SDK_INT >= 28) {
                add(ImageDecoderDecoder(context))
            } else {
                add(GifDecoder())
            }
        }
        .build()
    Image(
        painter = rememberImagePainter(
            imageLoader = imageLoader,
            data = imageID,
            builder = {
                size(OriginalSize)
            }
        ),
        contentDescription = null,
        modifier = modifier
    )
}

using the following dependencies

implementation "io.coil-kt:coil-compose:1.4.0"
implementation "io.coil-kt:coil-gif:1.4.0"
Hoby
  • 871
  • 11
  • 24