您的位置:首页 > 移动开发 > IOS开发

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"];
}


使用效果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios 图形