iOS动态图实现(1)
2015-12-22 19:23
435 查看
动态图的分类
GIF : GIF图形交换格式是一种位图图形文件格式,以8位色(即256种颜色)重现真彩色的图像,诞生在windows1.0的时代,已经有27年的历史,广泛的应用在图像的网络传输中。WEBP : 2010年谷歌推出的图片格式,专门用来在web中使用,只有opera和chrome支持。
APNG : 这东西是mozilla搞出来的,它是24位的,可以容纳1680万种颜色,也是为了取代GIF,目前有火狐和Safari支持。
各种格式的图片大小性能比较可以在 GIF vs APNG vs WEBP 中看到,腾讯的同学也开源了一些工具可以用来转换不同格式的动态图,比如 iSpatra 。
iOS动图的保存
iOS 的相册是支持保存 GIF 和 APNG 动图的,只是不能直接播放。用[ALAssetsLibrary writeImageDataToSavedPhotosAlbum:metadata:completionBlock]
可以直接把 APNG、GIF 的数据写入相册。如果图省事直接用
UIImageWriteToSavedPhotosAlbum()写相册,那么图像会被强制转码为 PNG。
目前来说,保存 UIImage 有三种方式:1.直接用
NSKeyedArchiver把 UIImage 序列化保存,2.用
UIImagePNGRepresentation()先把图片转为 PNG 保存,3.用
UIImageJPEGRepresentation()把图片压缩成 JPEG 保存。
实际上,NSKeyedArchiver 是调用了
UIImagePNGRepresentation进行序列化的,用它来保存图片是消耗最大的。苹果对 JPEG 有硬编码和硬解码,保存成 JPEG 会大大缩减编码解码时间,也能减小文件体积。所以如果图片不包含透明像素时,
UIImageJPEGRepresentation(0.9)是最佳的图片保存方式,其次是
UIImagePNGRepresentation()。
GIF动态图的播放
1.使用UIImageView来播放
//创建UIImageView,添加到界面 UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)]; [self.view addSubview:imageView]; //创建一个数组,数组中按顺序添加要播放的图片(图片为静态的图片) NSMutableArray *imgArray = [NSMutableArray array]; for (int i=1; i<7; i++) { UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"gif%02d.png",i]]; [imgArray addObject:image]; } //把存有UIImage的数组赋给动画图片数组 imageView.animationImages = imgArray; //设置执行一次完整动画的时长 imageView.animationDuration = 6*0.15; //动画重复次数 (0为重复播放) imageView.animationRepeatCount = 0; //开始播放动画 [imageView startAnimating]; //停止播放动画 - (void)stopAnimating; //判断是否正在执行动画 - (BOOL)isAnimating;
如果图片数量较多的话,会占用很大的内存,每帧的播放时间一样,失去了原有动态图的播放延迟时间
2.用UIWebView来显示动态图
//得到图片的路径 NSString *path = [[NSBundle mainBundle] pathForResource:@"happy" ofType:@"gif"]; //将图片转为NSData NSData *gifData = [NSData dataWithContentsOfFile:path]; //创建一个webView,添加到界面 UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 150, 200, 200)]; [self.view addSubview:webView]; //自动调整尺寸 webView.scalesPageToFit = YES; //禁止滚动 webView.scrollView.scrollEnabled = NO; //设置透明效果 webView.backgroundColor = [UIColor clearColor]; webView.opaque = 0; //加载数据 [webView loadData:gifData MIMEType:@"image/gif" textEncodingName:nil baseURL:nil];
无法控制进度,可控性不强,Web View 没有为在移动设备上播放 GIF 而做优化,经常会降低播放速度。同时也不能很好地控制回放过程或内存占用
3.使用自定义的view来播放动态图
其实使用自定义的view来播放动态图所采取的做法无非是给View加一个image属性,所以决定采用继承UIImageView和UIImage来实现动态图的播放。动态图控件的优劣主要要考虑一下因素:
1.CPU使用率
2.内存消耗
3.滑动显示的帧率
基于以上影响性能的考虑,决定通过一个可变数组缓存动态图的10帧图像,然后在读取当前帧的图像时,删除数组中缓存的第n帧图像,异步线程加载后面对应的第n+10帧图像,以此来达到减少内存消耗的目的,示例代码如下:
- (UIImage * )getFrameImageAtIndex:(NSUInteger)index { UIImage* frame = nil; @synchronized(self.frameImages) { frame = self.frameImages[index]; } if (!frame || [frame isKindOfClass:[NSNull class]]) { CGImageSourceStatus state = CGImageSourceGetStatus(_imageSource); CGImageRef image = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL); if (image != NULL) { frame = [UIImage imageWithCGImage:image scale:imageScale orientation:UIImageOrientationUp]; CFRelease(image); } } if (self.frameCount > productLimitNum) { [self.frameImages replaceObjectAtIndex:index withObject:[NSNull null]]; NSUInteger nextReadIdx = (index + productLimitNum); nextReadIdx %= self.frameCount; if([self.frameImages[nextReadIdx] isKindOfClass:[NSNull class]]) { dispatch_async(_serialQueue, ^{ CGImageSourceStatus state = CGImageSourceGetStatus(_imageSource); CGImageRef image = CGImageSourceCreateImageAtIndex(_imageSource, nextReadIdx, NULL); @synchronized(self.frameImages) { if (image != NULL) { [self.frameImages replaceObjectAtIndex:nextReadIdx withObject:[UIImage imageWithCGImage:image scale:imageScale orientation:UIImageOrientationUp]]; } else { [self.frameImages replaceObjectAtIndex:nextReadIdx withObject:[NSNull null]]; } CFRelease(image); } }); } } return frame; }
通过CADisplayLink来同步的刷新当前UIImageView显示的图像,刷新的频率根据相应的设备而定,一般会在60fps左右,最终达到图像动态播放的目的。
- (CADisplayLink *)displayLink { if (self.superview && self.animatedImage) { if (_displayLink) { return _displayLink; }else{ _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeCurrentFrame:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } } else { [_displayLink invalidate]; _displayLink = nil; } return _displayLink; } - (void)changeCurrentFrame:(CADisplayLink *)displayLink { if (self.currentFrameIndex >= self.animatedImage.frameCount) { return; } self.accumulator += fmin(displayLink.duration, 0.1); while (self.accumulator >= self.animatedImage.frameDurations[self.currentFrameIndex]) { self.accumulator -= self.animatedImage.frameDurations[self.currentFrameIndex]; if (++self.currentFrameIndex >= self.animatedImage.frameCount) { if (--self.loopCountdown == 0) { [self stopAnimating]; return; } self.currentFrameIndex = 0; } self.currentFrameIndex = MIN(self.currentFrameIndex, [self.animatedImage.images count] - 1); self.currentFrame = [self.animatedImage getFrameImageAtIndex:self.currentFrameIndex]; [self.layer setNeedsDisplay]; } }
除了通过CADisplayLink来达成动态图刷新之外还有两种方法也可以达到目的:
通过设置动画属性,为layer添CAKeyframeAnimation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"]; NSMutableArray *times = [NSMutableArray arrayWithCapacity:3]; CGFloat currentTime = 0; int count = _frameDelayTimes.count; for (int i = 0; i < count; ++i) { [times addObject:[NSNumber numberWithFloat:(currentTime/_totalTime)]]; currentTime += [[_frameDelayTimes objectAtIndex:i] floatValue]; } [animation setKeyTimes:times]; NSMutableArray *images = [NSMutableArray arrayWithCapacity:3]; for (int i = 0; i < count; ++i) { [images addObject:[_frames objectAtIndex:i]]; } [animation setValues:images]; [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; animation.duration = _totalTime; animation.delegate = self; animation.repeatCount = 5; [self.layer addAnimation:animation forKey:@"gifAnimation"];
通过NSTimer递归调用,传入每一帧的持续时间
scheduledTimerWithTimeInterval:(NSTimeInterval)time invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
YZImageView和YZImage的使用
#import "ViewController.h" #import "YZImageView.h" @interface ViewController () @property (weak, nonatomic) IBOutlet YZImageView *test1; @property (weak, nonatomic) IBOutlet YZImageView *test2; @property (weak, nonatomic) IBOutlet YZImageView *test3; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.test1.animatedImage = (YZAnimationImage *)[YZAnimationImage imageNamed:@"test1"]; self.test2.animatedImage = (YZAnimationImage *)[YZAnimationImage imageNamed:@"test2"]; self.test3.animatedImage = (YZAnimationImage *)[YZAnimationImage imageNamed:@"test3@2x"]; }
使用效果
相关文章推荐
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- C#实现图形位置组合转换的方法
- C#实现判断图形文件格式的方法
- C#实现图形路径变换的方法
- php生成图形验证码几种方法小结
- CentOS的图形安装及初始环境设置教程
- 讲解iOS开发中基本的定位功能实现
- js判断客户端是iOS还是Android等移动终端的方法
- IOS开发环境windows化攻略
- 浅析iOS应用开发中线程间的通信与线程安全问题
- 检测iOS设备是否越狱的方法
- .net平台推送ios消息的实现方法
- C#实现图形区域组合操作的方法
- php实现图形显示Ip地址的代码及注释