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
相关产品推荐
相关产品推荐

