UIImageView 序列幀動畫的實現以及內存的優化
最近筆者項目中又一次遇到了UIImageView的序列幀動畫,各種百度,為了不讓下一次遇到序列幀動畫的時候還需要百度,也為了讓后來查資料的人有一個系統的理解,我準備將這些百度來的資料以及自己的理解寫成一個demo供大家參考學習;
UIImageView動圖三種實現方式:
實現方式一: SDWebImage實現
接到需求最初的想法就是用第三方來實現播放動畫gif動畫了;用SDWebImage來實現gif的播放代碼如下:
- (void)achieveGifWithSDWebImage {
NSString *filePath = [[NSBundle bundleWithPath:[[NSBundle mainBundle] bundlePath]] pathForResource:@"icon_image.gif" ofType:nil];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
self.imageView.image = [UIImage sd_animatedGIFWithData:imageData];
}
內存占用情況
沒有加載動畫之前:這種實現方式省心省力,只需要自己手動加載一下資源就好,內存不用自己手動控制,就可以釋放;
實現方式二:UIImageView實現
其實UIImageView有一套自己播放gif圖的方式的,不過其播放的不是gif格式的動圖,而是一幀幀的進行播放的:
- (void)createAnimation {
// 1.將序列圖加入數組
NSMutableArray *imagesArray = [[NSMutableArray alloc] init];
for (NSInteger i = 1;i <= 26;i++)
{
// UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"icon_image_00%02ld.png",I]];
// [imagesArray addObject:image];
// 計算文件名
NSString *filename = [NSString stringWithFormat:@"icon_image_00%02ld.png",I];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:path];
[imagesArray addObject:image];
}
// 設置序列圖數組
self.imageView.animationImages = imagesArray;
// 設置播放周期時間
self.imageView.animationDuration = 0.1*imagesArray.count;
// 設置播放次數
self.imageView.animationRepeatCount = 0;
// 播放動畫
[self.imageView startAnimating];
}
注意:這里加載圖片資源沒有用到UIImage中的imageNamed:方法,而是用了imageWithContentsOfFile方法,有什么樣的區別呢?本文末尾有關于此問題的解答。
內存占用情況
此種方式加載動畫的內存占用情況與sd加載gif效果一樣,只是需要手動做一步釋放,否則占用內存不能及時釋放:
// 停止動畫
[self.imageView stopAnimating];
// 將圖片資源進行釋放
self.imageView.animationImages = nil;
// 加載到內存中的資源要進行釋放 最好用_imagesArray方式,self.imagesArray方式有的時候釋放不掉;
_imagesArray = nil;
這兩個種實現方式有一個共同的特點,就是動畫結束后沒有一個明確的回調,如果是一個動畫結束之后另外一個動畫緊接著開始了,只靠定時器來完成總是會出現這種或者那種問題,要么是動畫錯位,要么是因為網絡問題播放不流暢導致出各種不可預知的問題,這個時候,有一個能準確檢測到動畫結束的回調對我們來說是至關重要的,那么下面我們來看看如何進行實現?應該以何種方式進行實現呢?
能準確監視動畫結束事件的實現方式----UIImageview的關鍵幀動畫CAKeyframeAnimation
//存放圖片的數組
NSMutableArray *array = [NSMutableArray array];
for(NSUInteger i = 1;i < 26 ;i++) {
// UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"icon_image_00%02ld",i]];
// 計算文件名
NSString *filename = [NSString stringWithFormat:@"icon_image_00%02ld.png",I];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:path];
CGImageRef cgimg = image.CGImage;
[array addObject:(__bridge UIImage *)cgimg];
}
_imagesArray = array;
// 創建CAKeyframeAnimation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
// 動畫結束之后的回調
animation.delegate = self;
animation.duration = array.count * 0.1;
animation.repeatCount = 2;
// 設置animation的唯一標示,這樣在delegate回調的時候能夠區分開來
[animation setValue:@"animation1" forKey:@"customType"];
animation.values = array;
[self.imageView.layer addAnimation:animation forKey:@""];
在動畫結束的時候想釋放資源內存,只需要這樣就可以了:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
// 動畫結束回調
NSString *keyPathValue = [anim valueForKey:@"customType"];
if ([keyPathValue isEqualToString:@"animation1"]) {
NSLog(@"這個動畫結束了");
_imagesArray = nil;
}
}
兩種圖片加載方式的區別:
+(UIImage *)imageNamed:(NSString *)name
+(UIImage *)imageWithContentsOfFile:(NSString *)name
查閱官方文檔可以總結如下:
imageNamed:在加載圖片時會根據名字在主目錄中查找,首先會在內存緩存中查找,如果沒有再從磁盤緩存中獲取,之后系統會緩存該圖片到內存中。
imageWithContentsOfFile:僅加載圖片,圖像數據不會緩存。
因此我們可以得到這樣的結論:
- 當我們頻繁的使用一個圖片的時候(例如:在一個tableview的cell中會加載一個圖標,那么用imageNamed:方法效率會很高)使用imageNamed方法;
- 然而iOS的內存非常珍貴并且在內存消耗過大時,會強制釋放內存,即會遇到memory warnings。而在iOS系統里面釋放圖像的內存是一件比較麻煩的事情,有可能會造成內存泄漏。例如:當一個UIView對象的animationImages是一個裝有UIImage對象動態數組NSMutableArray,并進行逐幀動畫。當使用imageNamed的方式加載圖像到一個動態數組NSMutableArray,這將會很有可能造成內存泄露。原因很顯然的。
- 加載比較大的圖片的時候切記使用imageNamed:,應該使用imageWithContentsOfFile:;