FLAnimatedImageView處理gif過程


FLAnimatedImageView處理gif過程

時間控制原理

GIF圖片每一幀的delayTime可能都不一樣;

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM