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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 02:25:09