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

posted @ 2017-06-11 03:22  edisongz  阅读(4036)  评论(0编辑  收藏  举报