Glide 动图循环控制:播放次数、结束回调与资源清理怎么做
【免费下载链接】glideAn image loading and caching library for Android focused on smooth scrolling项目地址: https://gitcode.com/gh_mirrors/gl/glide
Glide 加载 GIF/WebP 动图时,默认行为是无限循环,而setLoopCount加动画结束回调是打破这个默认的关键。掌握它们之后,你能判断一个动图需求该在哪个环节介入,也能快速定位"播放次数不生效""结束后没停"这类问题的根源。
什么需求下你需要控制播放次数
三个典型场景,先对号入座:
- 引导类动效:首页新功能引导动画,用户首次进入播放 2 次后停在最后一帧,不干扰操作;触发条件是首次加载,需要控制的是一次性播放次数。
- 状态提示动画:搜索结果页的加载占位动图,正常时循环播放,切走页面必须停止,否则后台还在解码帧;这里要控制的是生命周期内的启停。
- 任务完成动画:上传成功后播放一次庆祝动画,播完弹出"查看结果"按钮;需要控制的不仅是次数,还有"播完这一刻"的回调时机。
共同点:只要动画结束要触发别的 UI 动作,就不能只靠无限循环,必须拿到"播放结束"这个事件。
理解三个角色:GifDrawable、帧加载器与循环计数
用一句话说清分工,方便你复述给别人听:
- GifDrawable是 Glide 给动图返回的 Drawable 对象,你从
into的资源回调里拿到它。它持有maxLoopCount字段,默认值是LOOP_FOREVER(-1),表示无限循环;LOOP_INTRINSIC(0)表示沿用动图文件里自带的循环次数,GIF 场景下就是 netscape 扩展块里的数值。 - GifFrameLoader(帧加载器)负责按帧解码和推进。每次
start()时 GifDrawable 会向它订阅,每解出一帧就回调onFrameReady()。 - 循环计数在
onFrameReady()里推进:当帧索引走到最后一帧时loopCount++,一旦loopCount >= maxLoopCount且上限不是 -1,就自动stop()并通知所有已注册的AnimationCallback。
由此可以推出两条重要性质:回调只在"因达到次数上限而停止"时触发,手动stop()不会触发;start()会重置loopCount为 0,所以重启播放就是再调一次start()。
最短验证路径:三步跑通
结论先说:在资源就绪的回调里设置次数,注册结束后要执行的逻辑,页面销毁时停掉并清理。不要试图在into之前设置,那时 Drawable 还不存在。
第一步,拿到资源后设置次数。用asGif()或带RequestListener的into都行:
// onResourceReady 回调内 if (resource instanceof GifDrawable) { gifDrawable = (GifDrawable) resource; gifDrawable.setLoopCount(2); // 只播 2 轮 }第二步,注册结束回调。registerAnimationCallback来自Animatable2Compat.AnimationCallback:
gifDrawable.registerAnimationCallback(drawable -> { // 仅当达到 setLoopCount 上限自动停止时触发 showRetryButton(); }); gifDrawable.start();第三步,在生命周期出口清理。调用链是:onDestroy -> stop() -> clearAnimationCallbacks(),防止回调持有页面引用导致泄漏:
@Override protected void onDestroy() { super.onDestroy(); if (gifDrawable != null) { gifDrawable.stop(); gifDrawable.clearAnimationCallbacks(); } }如果你走的是Target方式(SimpleTarget/CustomTarget),清理动作应放在onLoadCleared里,而不是只依赖 Activity 生命周期。仓库的 samples/giphy 示例展示了用RequestListener捕获 GifDrawable 的完整写法,可直接对照阅读。
三类故障信号与对应处理动作
信号一:setLoopCount 调了,动图仍无限循环。先查三个点:是否调用了asGif()(用asDrawable()或普通load().into()拿到的可能不是 GifDrawable);是否在资源真正就绪后才调用;是否误传了非法值(setLoopCount接受正整数、LOOP_FOREVER或LOOP_INTRINSIC,其他值会直接抛IllegalArgumentException)。定位方法很简单:在回调里打印resource.getClass(),确认类型再谈后续。
信号二:页面已销毁,动画回调仍在触发或内存持续增长。说明AnimationCallback持有 Activity 强引用且未注销。判断方法:在onDestroy打断点,确认执行了stop()和clearAnimationCallbacks();若回调写在匿名内部类里捕获了页面,优先改成弱引用持有,或把回调对象声明为成员变量以便精确unregisterAnimationCallback。
信号三:设了 3 次,视觉上像播了 4 次或 2 次。两个常见原因:一是在start()之后才设的次数没赶上第一轮计数(start()会重置计数,但设置时机与播放顺序要一致);二是LOOP_INTRINSIC模式下文件自带循环数与预期不符。处理动作:统一按"设次数 → 注册回调 → start"的固定顺序执行,且重启播放前不要自行维护额外计数器,以onAnimationEnd触发次数为准做验收。
性能、兼容与边界
- 尺寸:动图内存与目标尺寸平方级相关,
RequestBuilder上先用override(width, height)或sizeMultiplier约束,再谈次数控制,顺序反了会白白多占内存。 - 格式边界:
asGif()返回的始终是 GifDrawable,WebP 动图能否走这条路取决于解码器注册与系统版本,行为与 GIF 不完全一致,跨机型要实测。 - 可见性:GifDrawable 在
setVisible(false)时会自动停止解码、恢复可见后重启,列表滚动天然受益;但反过来,手动stop()后不会因重新可见而自动恢复,两者语义不同。 - 单帧边界:帧数为 1 时不进入解码循环,
onAnimationEnd不会触发,别把这类资源当动图处理。 - ROM 差异:低端机和部分定制 ROM 的解码速度不一,结束回调的到达时间会波动,UI 跳转不要依赖精确时长。
| 关键方法 | 触发时机 | 结果 |
|---|---|---|
setLoopCount(n) | 资源就绪后、start 前 | 覆盖默认无限循环 |
registerAnimationCallback | 任意,生效于下一次达标停止 | 达标停止时回调 |
start() | 动画未运行时 | 重置计数并开播 |
stop() | 任意 | 立即停,不触发结束回调 |
发布前检查清单
- 确认在资源就绪回调中拿到
GifDrawable实例后再调用setLoopCount - 确认加载入口用的是
asGif(),且资源确实是动图而非静态图 - 确认
setLoopCount传值为正整数、LOOP_FOREVER或LOOP_INTRINSIC之一 - 确认结束回调按"达标停止"语义使用,不依赖手动 stop 触发
- 确认
onDestroy/onLoadCleared中执行了stop与clearAnimationCallbacks - 确认回调不持有 Activity 强引用,或已有弱引用/成员变量方案
- 用
override约束了目标尺寸,并在低端机验证过播放与结束时机
核心实现位于 GifDrawable.java,帧推进逻辑在同目录的 GifFrameLoader.java,整体项目说明见 README.md。
【免费下载链接】glideAn image loading and caching library for Android focused on smooth scrolling项目地址: https://gitcode.com/gh_mirrors/gl/glide
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考