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等加鎖/解鎖操作;性能有保證。
