探究YYAnimatedImageView為什么可以顯示動態(tài)圖片

大家都知道如果想讓UIImageView顯示動態(tài)圖片,可以設(shè)置animationImages賦值一個圖片數(shù)組,然后設(shè)置一下動畫時間橱健,再開啟動畫∩沉總感覺使用很麻煩拘荡,而且如果是一個gif格式或者其他格式的動態(tài)圖,直接就無法使用了撬陵。后來在github上發(fā)現(xiàn)了YYImage,發(fā)現(xiàn)使用起來很方便珊皿,但同時也很好奇是怎么做到的,現(xiàn)在就以YYAnimatedImageView為主介紹一下這個庫是怎么實(shí)現(xiàn)顯示動態(tài)圖的袱结。廢話不多說亮隙,上圖

這個就是用來測試的gif

先寫兩句代碼,運(yùn)行一下垢夹,然后打斷點(diǎn)進(jìn)去看看是怎么加載的

YYAnimatedImageView* animatedView = [[YYAnimatedImageView alloc] init];

animatedView.frame=CGRectMake(0,0,200,150);

animatedView.center=self.view.center;

[self.view  addSubview:animatedView];

YYImage* image = [YYImage imageNamed:@"test.gif"];

animatedView.image= image;

這樣就能顯示動態(tài)圖了溢吻。

  • 首先從YYImage說起
  A YYImage object is a high-level way to display    animated image data

作者繼承UIImage類寫了YYImage,他自己的介紹是這是可以高效的展示動態(tài)圖的類,我們從imageNamed:方法看起促王,這個方法主要是去遍歷工程文件中是否有匹配的文件犀盟,如果找到路徑然后直接獲取圖片的二進(jìn)制文件,YYImage重寫了initWithData:scale:方法蝇狼,在這個里面使用了YYImageDecoder對圖片進(jìn)行界面阅畴,這就是YYImage為什么比較快的原因了

initWithData:scale:主要代碼
  • YYImageDecoder *decoder = [YYImageDecoder decoderWithData:datascale:scale]進(jìn)入YYImagecoder去看看是怎么處理的

updatedata.png

根據(jù)方法調(diào)用和參數(shù)傳遞,來到如上圖所示方法迅耘,是yyImageCoder第一個做實(shí)事的方法贱枣,YYImageDetectType先得到這個圖片的格式(比如PNG,JPEG,GIF等),具體怎么得出來的可以點(diǎn)進(jìn)去仔細(xì)看看颤专,大概就是獲得這個二進(jìn)制文件的前16字節(jié)纽哥,然后進(jìn)行匹配,得到圖片的格式栖秕,得到了圖片格式之后進(jìn)入下一步[self _updateSource]

updateSource.png

在這里作者對webp和apng格式的動畫進(jìn)行了專門的解碼優(yōu)化所以進(jìn)入不同方法春塌,總之這個方法就是對不同格式的圖片進(jìn)行解碼路由,我們點(diǎn)進(jìn)去_updateSourceImageIO看看怎么做的簇捍,看下主要代碼(去除了一些條件判斷)

//使用ImageIO框架去獲得圖片
//使用CGImageSourceCreateWithData獲得圖片類型是CGImageSourceRef
_source=CGImageSourceCreateWithData((__bridgeCFDataRef)_data,NULL);
//這里frameCount代表圖片的數(shù)量只壳,比如GIF其實(shí)就是一組圖片
//下面是一些不同類型的判斷
_frameCount = CGImageSourceGetCount(_source);
if (_type == YYImageTypeGIF) {
            //這字典打印出來是
            //FileSize = 487202;
            //"{GIF}" =     {
            //    HasGlobalColorMap = 1;
            //    LoopCount = 0;
            //};
            //  loopCount = 0 表示會無線循環(huán)當(dāng)前的gif
            CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL);
            if (properties) {
                CFTypeRef loop = CFDictionaryGetValue(properties, kCGImagePropertyGIFLoopCount);
                if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount);
                
                CFRelease(properties);
            }
        }
//建立一個數(shù)組,把每個圖片的索引暑塑,延遲時間等封裝成_YYImageDecoderFrame類
    //加入到數(shù)組中
    NSMutableArray *frames = [NSMutableArray new];
    for (NSUInteger i = 0; i < _frameCount; i++) {
        _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new];
        frame.index = i;
        frame.blendFromIndex = i;
        frame.hasAlpha = YES;
        frame.isFullSize = YES;
        [frames addObject:frame];
        //得到每一幀圖片的屬性
        //包括圖片的 寬  高   延遲時間  顏色空間等
        CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, i, NULL);
        if (properties) {
            NSTimeInterval duration = 0;
            NSInteger orientationValue = 0, width = 0, height = 0;
            CFTypeRef value = NULL;
            
            value = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
            if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &width);
            value = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
            if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &height);
            if (_type == YYImageTypeGIF) {
                CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
                //此處打斷點(diǎn) 輸出
                // {
                //    DelayTime = "0.07";
                //    UnclampedDelayTime = "0.07";
                // }
                //表示每一幀圖片的延遲時間吼句,也就是需要顯示的時間
                if (gif) {
                   
                    value = CFDictionaryGetValue(gif, kCGImagePropertyGIFUnclampedDelayTime);
                    if (!value) {
                        value = CFDictionaryGetValue(gif, kCGImagePropertyGIFDelayTime);
                    }
                    if (value) CFNumberGetValue(value, kCFNumberDoubleType, &duration);
                }
            }
            //對動畫中需要到的關(guān)鍵屬性進(jìn)行賦值
            
            frame.width = width;
            frame.height = height;
            frame.duration = duration;

然后把每一幀圖片加入到frames數(shù)組中,這基本上是YYImageCoder完成的大部分工作了

  • 我們再回到Y(jié)YImage中YYImageCoder還是之前那個initWithData:scale方法梯投,我加了注釋命辖,看一下
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
        YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
        UIImage *image = frame.image;
        if (!image) return nil;
        //這里返回的是第一幀的圖片
        self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];
        if (!self) return nil;
        _animatedImageType = decoder.type;
        //當(dāng)frameCount >1 也就是 當(dāng)圖片是動態(tài)圖的時候
        if (decoder.frameCount > 1) {
            //注意看這里,這里把decoder 當(dāng)做自己成員變量了
            //為什么要這樣做分蓖? 因?yàn)楫?dāng)時上面返回的是第一幀的圖片尔艇,但是對于frameCount > 1的動態(tài)圖,
           //當(dāng)然返回一張是不夠的么鹤,這里保留decoder终娃,在需要使用YYAnimatedImageView代理方法的時候可以通過decoder來返回不同索引的圖片
            _decoder = decoder;
            _bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
            _animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
        }
  • 好了,YYImage所做的工作已經(jīng)完成了蒸甜,我們現(xiàn)在來看看YYAnimatedImageView是怎么做最后的舞臺的
    setImage:withType:方法出發(fā)棠耕,來到- (void)imageChanged方法,同樣我也添加了注釋
YYAnimatedImageType newType = [self currentImageType];
    id newVisibleImage = [self imageForType:newType];
    NSUInteger newImageFrameCount = 0;
    BOOL hasContentsRect = NO;

    if ([newVisibleImage isKindOfClass:[UIImage class]] &&
        //這句話其實(shí)就是把newVisibleImage當(dāng)做代理對象使用
        //因?yàn)檫@里用的都是YYImage類型柠新,YYImage已經(jīng)實(shí)現(xiàn)了<YYAnimatedImage>代理方法
        [newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) {
        //得到圖片的個數(shù)
        newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount;
        if (newImageFrameCount > 1) {
            hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)];
        }
    }
    if (!hasContentsRect && _curImageHasContentsRect) {
        //這個是關(guān)閉默認(rèn)的隱式動畫窍荧,防止對自己的動畫播放產(chǎn)生影響,
        //有興趣的可以看看 core animation
        if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
            [CATransaction begin];
            [CATransaction setDisableActions:YES];
            //設(shè)置layer的顯示范圍是整個寄宿圖片
            self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
            [CATransaction commit];
        }
    }
    _curImageHasContentsRect = hasContentsRect;
    if (hasContentsRect) {
        CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
        [self setContentsRect:rect forImage:newVisibleImage];
    }
    
    if (newImageFrameCount > 1) {
        //如果是 圖片數(shù)量>1 針對動態(tài)圖
        //resetAnimated就是創(chuàng)建了一個CADisplayLink定時器去刷新圖片顯示
        [self resetAnimated];
        _curAnimatedImage = newVisibleImage;
        _curFrame = newVisibleImage;
        _totalLoop = _curAnimatedImage.animatedImageLoopCount;
        _totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
        [self calcMaxBufferCount];
    }
    [self setNeedsDisplay];
    //開始播放動畫
    [self didMoved];

[self resetAnimated]方法中恨憎,使用dispatch_once來保證這個imageView中只起一次定時器蕊退,同時把這個定時器加到mainRunLoop,模式默認(rèn)為NSRunLoopCommonModes郊楣,也就是說在你滑動的時候不會影響到動態(tài)圖的播放,同時添加進(jìn)通知中心瓤荔,對于關(guān)于內(nèi)存警告的通知净蚤,和后臺的通知進(jìn)行相應(yīng)的一些處理,定時器定時調(diào)起step:方法输硝,這個方法主要是做什么呢今瀑,

_time += link.duration;
        //拿到當(dāng)前索引圖片的延遲時間,也就是需要顯示的時間
        delay = [image animatedImageDurationAtIndex:_curIndex];
        //如果當(dāng)前的link.duration還沒到点把,直接返回等到下一次調(diào)起
        //就拿文章頭部的那個動態(tài)圖來說橘荠,每張圖顯示的時間大約在0.07秒左右
        //而CADisplayLink每次任務(wù)執(zhí)行的時間大約是0.016秒
        //所以不會用每次都刷新圖片顯示
        if (_time < delay) return;
        //如果調(diào)用了就用當(dāng)前的時間減去 當(dāng)前圖片需要顯示的時間
        _time -= delay;
        if (nextIndex == 0) {
            _curLoop++;
            if (_curLoop >= _totalLoop && _totalLoop != 0) {
                _loopEnd = YES;
                [self stopAnimating];
                //主動調(diào)起刷新layer,系統(tǒng)會調(diào)用displayLayer
                [self.layer setNeedsDisplay];
                return;
            }
        }

_curFrame就是當(dāng)前要顯示的圖片愉粤,_curFrame的賦值也在step中砾医,具體就不解釋了,然后就通過下面這句代碼完成了imageview的layer的寄宿圖的設(shè)置
layer.contents = (__bridge id)_curFrame.CGImage;
好了到這衣厘,YYAnimatedImageView就開始播放動態(tài)圖了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末压恒,一起剝皮案震驚了整個濱河市影暴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌探赫,老刑警劉巖型宙,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伦吠,居然都是意外死亡妆兑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門毛仪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搁嗓,“玉大人,你說我怎么就攤上這事箱靴∠俟洌” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵衡怀,是天一觀的道長棍矛。 經(jīng)常有香客問我,道長抛杨,這世上最難降的妖魔是什么够委? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮怖现,結(jié)果婚禮上茁帽,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好脐雪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布厌小。 她就那樣靜靜地躺著,像睡著了一般战秋。 火紅的嫁衣襯著肌膚如雪璧亚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天脂信,我揣著相機(jī)與錄音癣蟋,去河邊找鬼。 笑死狰闪,一個胖子當(dāng)著我的面吹牛疯搅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播埋泵,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼幔欧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丽声?” 一聲冷哼從身側(cè)響起礁蔗,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雁社,沒想到半個月后浴井,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霉撵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年磺浙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徒坡。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡撕氧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崭参,到底是詐尸還是另有隱情呵曹,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布何暮,位于F島的核電站奄喂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏海洼。R本人自食惡果不足惜跨新,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坏逢。 院中可真熱鬧域帐,春花似錦赘被、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至龙优,卻和暖如春羊异,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彤断。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工野舶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宰衙。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓平道,卻偏偏與公主長得像,于是被迫代替她去往敵國和親供炼。 傳聞我的和親對象是個殘疾皇子一屋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容