Swift 玩转gif

11954071-cd07deba2f814604

gif study

众所周知,iOS默认是不支持gif类型图片的显示的,但是我们项目中常常是需要显示gif为动态图片。那肿么办?第三方库?是的 ,很多第三方都支持gif , 如果一直只停留在用第三方上,技术难有提高。上版本的 Kingfisher 也支持gif ,研究了一番,也在网上搜索了一番,稍微了解了下iOS实现gif的显示,在此略做记录。

本篇文章要实现的效果如图:

gif显示效果

可以开始和暂停gif的播放,滑动时停止播放,这个简书也是这么做得,好多app为了滑动时顺畅,停止了gif。

下面要进入正文啦!

期待…

分解gif帧进行显示

我们一般从网络上下载的gif图片其实是将很多帧静态图片循环播放产生的动态效果,那么在iOS中,如果我们想要显示动态图,同样需要先把gif资源解析为一阵一阵的UIImage然后设定间隔时长,不断播放即可。思路是不是很简单呢?那么看看如何实现。

分几个步骤:

  1. 将gif图片转为NSData
  2. 根据NSData获取CGImageSource对象
  3. 获取帧数
  4. 根据帧数获取每一帧对应的UIImage对象和时间间隔
  5. 循环播放

首先我们需要引入import ImageIO , 提供了很多对图片操作的函数。

这里我们从网上down了一个gif的图片,其实下载也是一样的 ,我们需要的是NSData类型的数据,用NSURLSession下载也可以得到NSData类型的数据,这里下载的数据如何判断是否为gif呢?

Kingfisher 库中给出了解决方案,每种格式的图片前面几位都是固定的。所以只需要对比就能判断出类型,这里给出Kingfisher判断类型的代码。

有了这个扩展判断起来就方便很多了。

为了使demo简单,我们直接将gif放在本地沙盒。下载好直接拖进项目就OK了。

这样就可以很容易的得到NSData类型的数据

第一步已经完成啦。

然后通过CGImageSourceCreateWithData 方法创建一个CGImageSource 对象 。

这里的options是为了显示优化。提前解码,指定类型。

拿到CGImageSource 对象就可以为所欲为了。

先获取帧数,然后循环根据帧数获取对应的图片,然后获取没帧间隔时间。累加时间间隔得到总共的时间,把图片存在一个图片数组中。

有了这些参数,我们就可以播放gif了。

界面上随便拖出来一个 UIImageView 然后给以下属性赋值即可。

运行项目,发现gif动起来了。

14954071-767e5024f8b4adaa

happy…

原来gif也没那么难,哈哈… …

但是这样你添加一个开始和暂停的按钮

你会发现,暂停时白板,什么图都没有,而且滚动的时候也不会暂停。。。

15954071-0459b7b101d62bc9

吐血…

这只是个开始,后面的路还很长,坐好继续。

处理gif的暂停、播放 滑动暂停等


以下部分基本上算是对Kingfisher 的一个理解,我们继续。

简单说下思路,要实现暂停在某帧,滑动暂停某帧这个就不能用UIImageViewstartAnimating直接操作了,需要我们自己处理帧和动画,动画在Kingfisher中使用CADisplayLink处理的,写了一个UIImageView的子类AnimatedImageView,重写了startAnimatingstopAnimating 等方法。关于CADisplayLink不熟悉的,看这篇文章 – CADisplayLink , 需要滑动暂停就把 CADisplayLink 加到 NSDefaultRunLoopMode模式的runloop下。 关于对帧的处理单独写了一个Animator . 下面来看看具体实现。

Animator 类处理帧

首先定义一个结构体,里面就有两个属性UIImage 图像 和 NSTimeInterval 帧之间时间间隔。

接着就可以创建一个 Animator 并定义一些需要用的属性

然后是一个队数据操作的方法,因为Kingfiher是处理网络图片的,所以我这边处理方式略不同

这个方法就是前面的根据NSData 获取 CGImageSource 对象,以备后用。

然后写一个将每一帧转换为我们刚定义的结构体 AnimatedFrame 对象

就是根据imageSource获取CGImage再转为UIImage , 然后获取帧间隔时间,构建结构体。 很easy 。没啥说的。

下面还需要一个预备所有帧的方法

这里其实就是得到总帧数然后给animatedFrames赋值,Kingfisher这里使用了readuce,累加的方式pure 方法是将一个值转成一个单值数组。

根据下表取帧

当前帧和contentMode属性

AnimatedImageView-可以播放gif的ImageView

基本成型,还差一个更新当前帧的方法,暂时不处理,先看去用实现一个继承自UIImageViewAnimatedImageView 并声明几个属性。

这里利用 CADisplayLink 不断执行某个方法,等达到帧之间的间隔时间的时候就去更新UIImageViewlayercontens 属性。这个属性需要一个CGImage的对象。

为了防止AnimatedImageViewCADisplayLink 之间的循环引用,Kingfisher在AnimatedImageView 内部写了一个代理类。

就是通过TargetProxy 来调用 AnimatedImageView 中的 updateFrame 方法,大家可以先写一个空方法。

然后创建一个CADisplayLink对象,这里使用懒加载。

用这个self.displayLinkInitialized 标志 CADisplayLink 已经加载,然后用代理就调用自己的 updateFrame()方法

在添加个指定RunLoopMode的属性

Kingfisher 默认是NSRunLoopCommonModes 滑动不暂停,我这边换成NSDefaultRunLoopMode 滑动暂停 。

NSRunLoopCommonModes 包含两个模式 UITrackingRunLoopModeNSDefaultRunLoopMode , 其中UITrackingRunLoopMode 是滑动时候的模式
,如果只在 NSDefaultRunLoopMode 模式下,那滑动模式就不会执行CADisplayLink 的方法, NSTimer 也可以指定 模式。非本篇重点 ,这里就不细说了

kingfisher 是重写了 image 属性进行Animator的初始化和重置的 , 这里为了demo的easy 我们给 AnimatedImageView 新增一个属性,叫 gifData.

创建Animator对象 ,缓存帧。 这里didMove() 方法是处理自动播放的

后面会重写startAnimatingstopAnimating .

先来看 CADisplayLink 每次调用的方法updateFrame() , 这里默认是每秒60次 , 根据屏幕刷新频率。

要实现updateFrame() 放法首先要在,Animator 中添加一个更新当前帧的方法。上面提到的,现在可以来写了。

传入的durationdisplayLink.duration 默认是 1/60 秒,这里先对每次的duration进行累加,直到我们的帧间隔时间小于等于它了 才去获取当前帧和增加下标,返回true , 否则一直返回false

然后AnimatedImageView中的 updateFrame 方法就是调用那个方法,直到它返回true才进行处理,这里就是调用了layer.setNeedsDisplay()

layer.setNeedsDisplay() 会触发 displayLayer 方法,我们只要重写这个方法,就能处理每帧的显示了。

搞了这么多,终于到显示了,不容易呀。。。

这里重写了几个方法,都去调用了didMove

这里gif的暂停是利用了CADisplayLinkpaused属性控制的

这里displayLinkInitialized 判断CADisplayLink是否加载好了。

最后记得在对象销毁的时候吧displaylink也停掉

至此,所有基本功能已经全部OK了,使用也很简单。

默认是自动播放,可以手动设置。

文章比较长,可能描述的不是很到位,有啥不清楚可以留言交流。

github地址:https://github.com/smalldu/ImageDemo

1 1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部