我有一个gif,想把它放到我的应用程序中。我知道如何插入图像资源,但当我尝试添加gif时,它变成了静态图像。
DrawImage(image = +imageResource(R.drawable.gif))
有人尝试在Jetpack Compose中添加gif吗?我很难在网上找到相关的文档。
我有一个gif,想把它放到我的应用程序中。我知道如何插入图像资源,但当我尝试添加gif时,它变成了静态图像。
DrawImage(image = +imageResource(R.drawable.gif))
有人尝试在Jetpack Compose中添加gif吗?我很难在网上找到相关的文档。
这里的大部分答案已经过时了。截至coil 2.1.0,这是现在的操作方式。
Hoby之前回答的版本已经进行了更新。
implementation "io.coil-kt:coil-compose:2.1.0"
implementation "io.coil-kt:coil-gif:2.1.0"
@Composable
fun GifImage(
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val imageLoader = ImageLoader.Builder(context)
.components {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.build()
Image(
painter = rememberAsyncImagePainter(
ImageRequest.Builder(context).data(data = R.drawable.YOUR_GIF_HERE).apply(block = {
size(Size.ORIGINAL)
}).build(), imageLoader = imageLoader
),
contentDescription = null,
modifier = modifier.fillMaxWidth(),
)
}
/storage/emulated/0/Android/data/com.example.messageapp/files/abW93.gif
。我该如何在上述代码中使用它? - undefined1.3.0
开始,Jetpack Compose的版本中添加了gif支持。 因此,您可以使用现有的coil文档来支持gif解码。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设置代码:
// 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),
...
)
0.1.0-dev16
中使用以下代码(取自https://github.com/luca992/coil-composable/blob/master/coil-composable/src/androidMain/kotlin/com/luca992/compose/image/CoilImage.kt并进行修改)来显示动态GIF图像: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
}
}
implementation 'io.coil-kt:coil:0.11.0'
implementation 'io.coil-kt:coil-gif:0.11.0'
使用方法如下:
setContent {
CoilImage("https://example.com/image.gif")
}
可以使用以下代码轻松实现
@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
)
}
implementation "io.coil-kt:coil-compose:1.4.0"
implementation "io.coil-kt:coil-gif:1.4.0"
我在这里采用了this的解决方案:https://github.com/jaredsburrows/android-gif-example/commit/5690523c6dc40c435c3d81868d89ba26d21e3663,因为它已经从Accompanist here中删除。
class ImageService @Inject constructor(@ApplicationContext private val context: Context) {
/** Compose views */
fun loadGif(
imageUrl: String,
thumbnailUrl: String,
onResourceReady: (GifDrawable?) -> Unit,
onLoadFailed: () -> Unit,
) {
loadGif(imageUrl)
.override(SIZE_ORIGINAL, SIZE_ORIGINAL)
.thumbnail(loadGif(thumbnailUrl))
.into(object : CustomTarget<GifDrawable>() {
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
onLoadFailed.invoke()
}
override fun onLoadCleared(placeholder: Drawable?) {
onLoadFailed.invoke()
}
override fun onResourceReady(
resource: GifDrawable,
transition: Transition<in GifDrawable>?,
) {
onResourceReady.invoke(resource)
}
})
}
/** ImageViews */
fun loadGif(
imageUrl: String,
thumbnailUrl: String,
imageView: ImageView,
onResourceReady: () -> Unit,
onLoadFailed: (GlideException?) -> Unit,
) {
loadGif(imageUrl)
.override(SIZE_ORIGINAL, SIZE_ORIGINAL)
.thumbnail(loadGif(thumbnailUrl))
.listener(
object : RequestListener<GifDrawable> {
override fun onResourceReady(
resource: GifDrawable?,
model: Any?,
target: Target<GifDrawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
onResourceReady.invoke()
return false
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<GifDrawable>?,
isFirstResource: Boolean
): Boolean {
onLoadFailed.invoke(e)
return false
}
}
)
.into(imageView)
.clearOnDetach()
}
private fun loadGif(imageUrl: String): RequestBuilder<GifDrawable> {
return GlideApp.with(context)
.asGif()
.transition(withCrossFade())
.load(imageUrl)
}
}
composeView.setContent {
val showProgressBar = remember { mutableStateOf(true) }
val state = remember { mutableStateOf<GifDrawable?>(null) }
GifTheme {
// Load images - 'tinyGifPreviewUrl' -> 'tinyGifUrl'
imageService.loadGif(
imageUrl = imageInfoModel.tinyGifUrl,
thumbnailUrl = imageInfoModel.tinyGifPreviewUrl,
onResourceReady = { resource ->
showProgressBar.value = false
state.value = resource
},
onLoadFailed = {
showProgressBar.value = false
state.value = null
},
)
// Show loading indicator when image is not loaded
if (showProgressBar.value) {
CircularProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.height(128.dp)
.padding(all = 24.dp),
)
} else {
Image(
painter = rememberDrawablePainter(drawable = state.value),
contentDescription = stringResource(id = R.string.gif_image),
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(135.dp),
)
}
}
}