Jetpack Compose中如何监听GIF第一循环播放结束的回调?
检测Jetpack Compose中GIF第一循环播放结束的时机
需求:需要在GIF播放完第一循环时触发回调。
提供的Jetpack Compose代码如下:@Composable fun AsyncGIFLoader( ) { 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.mygif).apply(block = { size(Size.ORIGINAL) }).build(), imageLoader = imageLoader ), contentDescription = null, modifier = Modifier.fillMaxWidth(), ) }曾尝试使用ImageLoader的onSuccess、onError回调,但这些回调仅在GIF加载成功时触发,并不会在GIF播放完第一循环时触发。请问是否有办法检测GIF第一循环播放结束的时机?
解决方案
方法1:监听Coil GifPainter的帧变化
Coil加载GIF后,内部会使用GifPainter处理播放逻辑。我们可以通过监听它的帧索引变化,判断第一循环是否完成:
@Composable fun AsyncGIFLoader(onFirstLoopComplete: () -> Unit) { val context = LocalContext.current val imageLoader = ImageLoader.Builder(context) .components { if (SDK_INT >= 28) { add(ImageDecoderDecoder.Factory()) } else { add(GifDecoder.Factory()) } } .build() val painter = rememberAsyncImagePainter( ImageRequest.Builder(context) .data(R.drawable.mygif) .size(Size.ORIGINAL) .build(), imageLoader = imageLoader ) // 标记是否已触发过回调,避免重复执行 val hasTriggered = remember { mutableStateOf(false) } LaunchedEffect(painter.state) { if (painter.state is AsyncImagePainter.State.Success && !hasTriggered.value) { val gifPainter = (painter.state as AsyncImagePainter.State.Success).painter as? GifPainter gifPainter?.let { snapshotFlow { it.currentFrameIndex } .distinctUntilChanged() .collect { frameIndex -> // 当前帧是最后一帧,且未触发过回调时,执行逻辑 if (frameIndex == it.frameCount - 1 && !hasTriggered.value) { hasTriggered.value = true onFirstLoopComplete() } } } } } Image( painter = painter, contentDescription = null, modifier = Modifier.fillMaxWidth(), ) }
方法2:解析GIF总时长,用定时器触发
如果不想依赖Coil内部实现,可以手动解析GIF的总播放时长,用定时器在对应时间点触发回调:
// 先添加依赖:implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.28' @Composable fun AsyncGIFLoader(onFirstLoopComplete: () -> Unit) { val context = LocalContext.current val imageLoader = ImageLoader.Builder(context) .components { if (SDK_INT >= 28) { add(ImageDecoderDecoder.Factory()) } else { add(GifDecoder.Factory()) } } .build() val painter = rememberAsyncImagePainter( ImageRequest.Builder(context) .data(R.drawable.mygif) .size(Size.ORIGINAL) .build(), imageLoader = imageLoader ) val hasTriggered = remember { mutableStateOf(false) } LaunchedEffect(painter.state) { if (painter.state is AsyncImagePainter.State.Success && !hasTriggered.value) { // 解析GIF获取第一循环的总时长 val gifDrawable = GifDrawable(context.resources, R.drawable.mygif) val totalLoopDuration = gifDrawable.totalDuration delay(totalLoopDuration.toLong()) if (!hasTriggered.value) { hasTriggered.value = true onFirstLoopComplete() } // 记得回收资源 gifDrawable.recycle() } } Image( painter = painter, contentDescription = null, modifier = Modifier.fillMaxWidth(), ) }
注意点
- 方法1直接对接Coil的播放逻辑,适配性更强,但需注意Coil版本更新可能带来的变化
- 方法2需要额外依赖第三方库,使用后要及时回收
GifDrawable避免内存泄漏 - 两种方法都通过
hasTriggered状态控制回调仅执行一次
内容的提问来源于stack exchange,提问作者dev mobile
相关产品推荐
相关产品推荐

