SDWebImage 如何用 SDAnimatedImagePlayer 在无 UIView 环境下播放动画图片?
【免费下载链接】SDWebImageAsynchronous image downloader with cache support as a UIImageView category项目地址: https://gitcode.com/GitHub_Trending/sd/SDWebImage
当运行环境里没有UIImageView可用时(README 中给出的典型例子是WatchKit和CALayer),SDAnimatedImageView就无法直接使用,动画 GIF/APNG 也就没有现成的播放载体。SDWebImage 5.x 把SDAnimatedImageView背后的播放引擎抽成了独立类SDAnimatedImagePlayer:它按帧向动画数据源取帧,通过animationFrameHandler把每一帧的UIImage回调给你,由你自己决定把这一帧画到哪里——WKInterfaceImage、CALayer或任何自定义渲染面。
本文的操作路径来自仓库内的头文件、实现和 Watch Demo 扩展源码,读者可以在无 UIView 的环境中完成一次完整的动画播放。
适用前提
以下事实来自 README.md 的 Requirements 一节,安装和运行前先确认环境满足:
- 平台:iOS 9.0+ / tvOS 9.0+ / watchOS 2.0+ / macOS 10.11+(Catalyst 需 10.15)/ visionOS 1.0+,Xcode 15.0+;
- 通过 CocoaPods 安装:
pod 'SDWebImage', '~> 5.0'README 同时提供 Swift Package Manager 安装方式,见该文档 "Installation with Swift Package Manager" 小节。
另外两个硬约束来自 SDAnimatedImagePlayer.h 与 SDAnimatedImagePlayer.m:
- 播放器的数据源必须是
SDAnimatedImageProvider协议实现,官方给出的例子是SDAnimatedImage、SDImageGIFCoder等; - 若 provider 的
animatedImageFrameCount小于 1,initWithProvider:直接返回nil,拿不到播放器实例。也就是说单帧图片不能走这条路径,需要在创建前自行检查帧数并保留回退逻辑(SDAnimatedImage.h 中也有同样提醒:帧数 ≤ 1 时协议方法会返回 nil 或 0 值)。
播放器的工作机制
SDAnimatedImagePlayer内部用SDDisplayLink挂在主 RunLoop 上逐帧驱动(见实现文件中displayLink相关代码)。你只需要关心接口契约:
- 状态属性(均支持 KVO):
currentFrame(当前帧图片)、currentFrameIndex(当前帧索引,从 0 起)、currentLoopCount(本轮播放以来的循环次数)、isPlaying(是否在播放); - 回调:
animationFrameHandler在每帧变化时触发,参数是帧索引和帧图片;animationLoopHandler在每轮循环结束时触发; - 可控参数:
playbackRate(默认 1.0,0.0-1.0减速,> 1.0加速,0.0停止,负值暂不支持)、playbackMode(Normal / Reverse / Bounce / ReversedBounce 四种播放模式)、totalLoopCount(默认取动画自身的循环次数,0 表示无限循环)、maxBufferSize(帧缓冲上限,0表示按当前内存自动计算,1表示不缓存缓冲,NSUIntegerMax表示全部缓存)、runLoopMode(默认多核设备用NSRunLoopCommonModes,单核设备用NSDefaultRunLoopMode)。
SDAnimatedImageView的player属性暴露的就是同一个类,注释里写明它"驱动 Animated ImageView 或任何渲染用途,比如 CALayer/WatchKit/SwiftUI 渲染",可以把它当作理解该类的参照。
完整操作步骤:以 WatchKit 为例
仓库自带了一份完整可用的示例:Examples/SDWebImage Watch Demo Extension/InterfaceController.m。下面按该示例拆解成可复用的步骤,示例中的 URL 是仓库 Demo 自带的演示地址,实际使用请替换为你自己的图片地址。
第 1 步:拿到SDAnimatedImage实例
Demo 通过分类方法从网络加载,并用 context 指定解码出的动画图类:
NSString *urlString = @"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"; // 仓库 Demo 示例地址,替换为自己的地址 [wself.animatedImageInterface sd_setImageWithURL:[NSURL URLWithString:urlString] placeholderImage:nil options:SDWebImageProgressiveLoad context:@{SDWebImageContextAnimatedImageClass : SDAnimatedImage.class} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { // 见第 2 步 }];本地资源同样可行:SDAnimatedImage提供imageNamed:、imageWithData:、initWithData:scale:等创建入口。
第 2 步:校验类型并创建播放器
在 completed 回调里先确认解码结果确实是SDAnimatedImage,再创建播放器:
if (![image isKindOfClass:[SDAnimatedImage class]]) { return; } self.player = [SDAnimatedImagePlayer playerWithProvider:(SDAnimatedImage *)image];第 3 步:在animationFrameHandler里渲染帧
把每一帧交给你的渲染面。WatchKit 下就是对WKInterfaceImage调setImage::
__weak typeof(self) wself = self; self.player.animationFrameHandler = ^(NSUInteger index, UIImage * _Nonnull frame) { [wself.animatedImageInterface setImage:frame]; };换成CALayer时,这里改为更新layer.contents即可——播放器不关心帧被画到哪里,这是它与SDAnimatedImageView的本质区别。
第 4 步:启动播放
[self.player startPlaying];startPlaying同时承担"恢复先前暂停的动画"的职责;pausePlaying保留当前帧索引和循环计数,stopPlaying则会把帧索引和循环计数重置。
验证播放是否生效
文档给出了三类可直接核对的判断方式:
isPlaying:startPlaying之后读取该属性应为YES,stopPlaying/pausePlaying之后为NO;- KVO 状态:
currentFrameIndex、currentLoopCount均支持 KVO,播放过程中索引应逐帧变化、循环计数每轮 +1;animationLoopHandler每轮也会回调一次; - 视觉验证:
animationFrameHandler持续被调用,WKInterfaceImage上的画面逐帧变化,Demo 中正是以此验证 389 帧 GIF 在 Apple Watch 上的播放。
需要精确控制时可用seekToFrameAtIndex:loopCount:跳到指定帧(帧索引超出totalFrameCount时调用会被忽略),以及clearFrameBuffer手动清空帧缓冲(默认pausePlaying/stopPlaying不会清缓冲,缓存会保留给下次启动用)。
参数取舍与限制
- 内存敏感设备上的帧缓冲:Demo 源码中有一段值得注意的注释——用
WKInterfaceImage的简单动画方式播放该 389 帧 GIF 时,Apple Watch 会消耗 800MB 以上内存并触发 OOM;而SDAnimatedImagePlayer路径与SDAnimatedImageView同后端,是 Demo 推荐的做法。这是仓库 Demo 给出的实测描述,非通用承诺。 maxBufferSize:解码开销高的格式(如 Animated WebP 软解)下用它调节缓冲帧数;取值语义见上文"可控参数"。- 渐进式加载:provider 内容可以是渐进更新的(progressive animation),但新帧到达后需要你自行更新
totalFrameCount;配合seekToFrameAtIndex:loopCount:可跳过指定帧。 - 播放速率:
playbackRate为负目前不受支持,会停止动画;反向播放请使用playbackMode中的 Reverse/Bounce 模式。 - 播放器生命周期:实现中在
dealloc时会自动注销帧池,持有player的视图控制器退出时释放即可;页面不可见时建议调用stopPlaying停止驱动。
完成以上步骤后,无 UIView 环境下就能稳定驱动动画图片:类型校验通过、isPlaying为YES且帧画面逐帧更新,即为播放成功。若要接入SDProgressiveImageCoder等高级解码路径,可参考 SDAnimatedImage.h 中initWithAnimatedCoder:scale:的说明。
【免费下载链接】SDWebImageAsynchronous image downloader with cache support as a UIImageView category项目地址: https://gitcode.com/GitHub_Trending/sd/SDWebImage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考