FLAnimatedImageView处理gif过程
FLAnimatedImageView处理gif过程
时间控制原理
GIF图片每一帧的delayTime可能都不一样;
在展示下一帧的时间控制机制,不能根据以第一帧为准;
或总动画时长除以帧数来简单做平均值为准,
都是不太好的方案。
FLAnimatedImageView的控制方式,读取每一帧的delayTime算出最大公约数,用CADisplayLink来控制时间的,比如说(如下图),
第二帧到第三帧的控制:第二帧的delayTime=2s,第三帧的delayTime=3s,如果第二帧没到时间,FLAnimatedImageView的image数据保持不变,时间一到就从FLAnimatedImage中获取image,赋值给ImageView。
CADisplayLink处理过程
首先startAnimating中的CADisplayLink初始化,为了防止retain cycle 用了NSProxy weak语义的property,也可以用
__weak typeof(self) wself = self; ... // block中处理 __strong typeof(wself) sself = wself; if (!sself) { return; } // 紧跟处理code
反正都与weak有关,都是固定套路了。
CADisplayLink的frameInterval,frameInterval = 1时,refreshRate = 60Hz,frameInterval = 2时,refreshRate = 30Hz;
此处设置的
const NSTimeInterval kDisplayRefreshRate = 60.0; // 60Hz // 最小的frameInterval = 1 self.displayLink.frameInterval = MAX([self frameDelayGreatestCommonDivisor] * kDisplayRefreshRate, 1);
接下来就是最关键的处理方法- (void)displayDidRefresh:(CADisplayLink *)displayLink
;
1,如果self.needsDisplayWhenImageBecomesAvailable==YES
,调用[self.layer setNeedsDisplay];
,标记layer需要刷新,下一次runloop中displayLayer方法会被调用;然后设置self.needsDisplayWhenImageBecomesAvailable=NO
,不到下一帧的时间,不改变ImageView的内容;
赋值代码相当简单:
- (void)displayLayer:(CALayer *)layer { // 从 image 方法中取currentFrame作为像是内容 layer.contents = (__bridge id)self.image.CGImage; }
2,累加时间值accumulator跟当前帧的delayTime比对,注释如下:
/** * duration属性提供了每帧之间的时间,也就是屏幕每次刷新之间的的时间,一个略小的值; * frameInterval相当一个>=1的固定值,每次refresh,accumulator都累加一小段时间 **/ self.accumulator += displayLink.duration * displayLink.frameInterval; // While-loop first inspired by & good Karma to: https://github.com/ondalabs/OLImageView/blob/master/OLImageView.m /** * 1,如果self.accumulator < 当前的delayTime,直接跳过,下一次refresh,accumulator再累加,相当于这一次image的内容不变; * 2,只有当累加值self.accumulator >= 当前的delayTime(说明已经是累加到这一帧的delayTime的最后,下一帧的开头了),进入while循环,循环内部每次将累加器减掉delayTime(accumulator又回到0状态),以便跳出循环;currentFrameIndex累加,下次refresh时,获取下一帧图像数据; * 3,如果到达最后一帧,循环次数loopCountdown--,又跳转到首帧图像; * 4,最主要的一步标记self.needsDisplayWhenImageBecomesAvailable = YES;下一次refresh时,执行[self.layer setNeedsDisplay]; 进行新一帧图像数据的刷新。 **/ while (self.accumulator >= delayTime) { self.accumulator -= delayTime; self.currentFrameIndex++; if (self.currentFrameIndex >= self.animatedImage.frameCount) { // If we've looped the number of times that this animated image describes, stop looping. self.loopCountdown--; if (self.loopCompletionBlock) { self.loopCompletionBlock(self.loopCountdown); } if (self.loopCountdown == 0) { [self stopAnimating]; return; } self.currentFrameIndex = 0; } // Calling `-setNeedsDisplay` will just paint the current frame, not the new frame that we may have moved to. // Instead, set `needsDisplayWhenImageBecomesAvailable` to `YES` -- this will paint the new image once loaded. self.needsDisplayWhenImageBecomesAvailable = YES; }
至此就时间控制过程基本处理完成。
另外FLAnimatedImage类,专门根据gif图片大小来限制缓存多少帧,当遇到memoryWarning时,如何重试处理;多次遇到memoryWarning,固定缓存帧数等等功能,而且没有用到dispatch_semaphore等加锁/解锁操作;性能有保证。