You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose使用Coil实现图片饱和度动画及onStart重复触发修复

问题原因
  • 核心问题是你没有将ValueAnimator、ImageRequest这类需要持久化的实例用Compose的remember修饰,导致每次Composable重组时都会重新生成全新的实例。
  • 动画更新时修改了imgConfig这个可变状态,会触发当前Composable函数重组,重组过程中新生成的ImageRequest会重新触发图片加载流程,进而反复打印onStart日志,甚至会反复触发动画形成死循环。
  • 直接在Composable作用域创建的ValueAnimator没有和组件生命周期绑定,还存在内存泄漏风险。
修复方案

方案1:保留原有ValueAnimator逻辑修复

将需要持久化的实例用remember缓存,同时绑定组件生命周期管理动画销毁:

var imgConfig by remember { mutableStateOf(ColorMatrix()) }

// 用remember缓存动画实例,仅初始化一次
val animation = remember {
    ValueAnimator.ofFloat(0F, 1F).apply {
        duration = 3000
        addUpdateListener {
            imgConfig = ColorMatrix().apply {
                setToSaturation(it.animatedFraction)
            }
        }
    }
}

// 组件销毁时取消动画避免泄漏
DisposableEffect(Unit) {
    onDispose {
        animation.cancel()
    }
}

// 用remember缓存图片请求,仅在url变化时重新创建
val request = remember(url) {
    ImageRequest.Builder(LocalContext.current)
        .data(url)
        .placeholder(ColorDrawable(android.graphics.Color.LTGRAY))
        .crossfade(true)
        .listener(
            onStart = {
                Log.d("AnimationImgLoader", "onStart")
            },
            onCancel = {
                Log.d("AnimationImgLoader", "onCancel")
            },
            onError = { _: ImageRequest, throwable: Throwable ->
                Log.e("AnimationImgLoader", "onError ${throwable.message}")
            },
            onSuccess = { _: ImageRequest, _: ImageResult.Metadata ->
                Log.d("AnimationImgLoader", "onSuccess")
                // 先取消正在运行的动画避免重复触发
                if (animation.isRunning) animation.cancel()
                animation.start()
            }
        )
        .build()
}

Image(
    painter = rememberImagePainter(request = request),
    contentDescription = "RemoteImage",
    colorFilter = ColorFilter.colorMatrix(imgConfig)
)

方案2:改用Compose原生动画API(更推荐)

直接使用Compose原生动画API,适配Compose生命周期,性能更优:

import androidx.compose.animation.core.animate
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.LinearEasing

var saturation by remember { mutableFloatStateOf(0f) }
var isImageLoaded by remember { mutableStateOf(false) }

// 图片加载成功后自动触发饱和度动画
LaunchedEffect(isImageLoaded) {
    if (isImageLoaded) {
        animate(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = tween(durationMillis = 3000, easing = LinearEasing)
        ) { value, _ ->
            saturation = value
        }
    }
}

val request = remember(url) {
    ImageRequest.Builder(LocalContext.current)
        .data(url)
        .placeholder(ColorDrawable(android.graphics.Color.LTGRAY))
        .crossfade(true)
        .listener(
            onSuccess = { _, _ ->
                isImageLoaded = true
            }
        )
        .build()
}

Image(
    painter = rememberImagePainter(request = request),
    contentDescription = "RemoteImage",
    colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(saturation) })
)

内容的提问来源于stack exchange,提问作者Mr.Bendan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 04:06:01