iOS动画基础

前言

  这边博客主要是总结一下最近看到iOS动画开发的一些基础知识。主要包含如下一些知识点。主要参考自《iOS开发实战:从入门到上架APPsotre》

1
2
3
4
5
6
7
8
9
10
11
12
1、使用UIImageView播放图片组动画
2、使用block回调创建UIView层过渡动画
3、使用block回调创建UIView层转场动画
4、使用commit方式创建UIView层过渡动画
5、使用commit方式创建UIView层转场动画
6、CAlayer层在UI开发中的应用
7、各种CALayer子类的用法
8、coreAnimation核心动画框架的用法
9、视图的transform变换
10、粒子效果动画
11、iOS中播放GIF动态图的方法
12、Flappy Bird动画小游戏

使用UIImageView播放图片组动画

  使用UIImageview的animationImages属性将需要播放的图片加载进去,设置播放时长,和重复次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(30, 100, 170, 460)];
NSMutableArray *imageArray = @[].mutableCopy;
for(int i = 1; i <= 4;i++) {
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"VC1Image%d",i]];
[imageArray addObject:image];
}
[self.view addSubview:imageView];
imageView.animationImages = imageArray;
imageView.animationDuration = 0.4;
imageView.animationRepeatCount = 0; ///无限循环
[imageView startAnimating];
}

代码运行后就会出现四张图无限轮播的动画效果。

UIView层动画的应用

  一般的开发中,对于UIView层的动画是使用非常广泛的。iOS提供在UIView层上进行动画操作的类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// NSTimeInterval:动画持续时间
///^(void)animations 需要执行的动画内容
[UIView animateWithDuration:<#(NSTimeInterval)#> animations:<#^(void)animations#>]

///NSTimeInterval:同上
///^(void)animations :同上
/// ^(BOOL finished)completion 动画完成需要执行的动作
[UIView animateWithDuration:<#(NSTimeInterval)#> animations:<#^(void)animations#> completion:<#^(BOOL finished)completion#>]

/// UIViewAnimationOptions 动画执行的配置参数 枚举类型,比如淡入淡出效果等
/// 动画配置参数的取值可以参考源码中的枚举定义
/// delay 动画延迟执行时间
[UIView animateWithDuration:<#(NSTimeInterval)#> delay:<#(NSTimeInterval)#> options:<#(UIViewAnimationOptions)#> animations:<#^(void)animations#> completion:<#^(BOOL finished)completion#>]

// usingSpringWithDamping 取值0-1,设置阻尼度
// initialSpringVelocity 阻尼初速度
[UIView animateWithDuration:<#(NSTimeInterval)#> delay:<#(NSTimeInterval)#> usingSpringWithDamping:<#(CGFloat)#> initialSpringVelocity:<#(CGFloat)#> options:<#(UIViewAnimationOptions)#> animations:<#^(void)animations#> completion:<#^(BOOL finished)completion#>]
1
2
3
4
5
6
7
8
9
10
11
12
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 40, 40)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
NSTimeInterval times = 3;
[UIView animateWithDuration:times animations:^{
view.frame = CGRectMake(200, 200, 80, 80);
view.backgroundColor = [UIColor redColor];
}];
}

  运行代码可以看到图从蓝色变为红色 位置偏移,大小变化,持续时间3秒,其他几种动画效果参考demo

UIView层过渡动画支持的属性

  动画的执行还可以进行嵌套,可以将动画分成几个部分,执行完某一部分后在其回调中执行下一部分,下面列出一些动画过渡中可以支持的属性

1、frame:给视图的位置和尺寸实现过渡的动画效果。
2、bounds:给视图内部的坐标原点改变和尺寸变化增加过渡效果
3、center:为视图的
4、transform:给视图的几何变换增加过渡动画
5、alpha:给视图透明度增加过渡动画
6、backgroundColor:给视图背景色增加动画
7、contentStrech给UIImageview改变图片拉伸方式增加动画

  bounds和frame.使用cgRect设置时,后两个参数决定大小,前两个参数中,frame决定当前视图在父视图中的位置,bounds决定子视图在自身中的坐标原点的位置

使用commit方式进行UIView动画创建

  在iOS4之前使用传统的commit的方式创建UIView层的动画。这种方式包含四个步骤,1设置动画开始标志,2、设置动画属性,3、设置动画过渡效果,4、提交动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIView *view = [UIView new];
view.frame = CGRectMake(100, 100, 80, 80);
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
/// 动画开始标志
[UIView beginAnimations:@"test" context:nil];
[UIView setAnimationDelegate:self];
/// 动画属性
[UIView setAnimationWillStartSelector:@selector(start)];
[UIView setAnimationDidStopSelector:@selector(stop)];
[UIView setAnimationDuration:3];
[UIView setAnimationDelay:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationRepeatAutoreverses:YES];
[UIView setAnimationRepeatCount:3];
/// 动画过渡效果
view.backgroundColor = [UIColor blueColor];
view.frame = CGRectMake(200, 200, 80, 80);
/// 提交动画
[UIView commitAnimations];
}

  对比上下两种动画,commit的方式更为传统,需要编写的代码也更多,因为是在iOS4之前,iOS4之后引入UIView的类方法的动画到现在应该是所有版本都适配的,现在基本没有iOS4以下的设备。而且commit创建动画的方式已经在iOS13以后被废弃了。所以还是尽量使用UIView类方法创建动画的方式更好。

UIView转场动画

   相对于过渡动画,转场动画更多用于两个UIView视图的切换或者重绘某个视图。iOS中,UIView的转场动画有两种,一种是对UIView视图内容进行重新绘制,一种是使用新的视图替换旧视图的转场。
   在实际开发中开发者会遇到这样的需求:一个UIView视图中有许多子视图,当用户数据变化时,子视图控件需要重新布局,使得原本视图看来变成新的视图。因为视图的重绘是在瞬间完成的,如果不添加任何转场效果,就会出现闪动,体验效果很差。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 80, 80)];
view.backgroundColor = [UIColor redColor];
view2 = [[UIView alloc]initWithFrame:CGRectMake(200, 200, 80, 80)];
view2.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];


}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
/// 视图自身变化进行转场
[UIView transitionWithView:view duration:3 options:UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowAnimatedContent animations:^{
self->view.backgroundColor = [UIColor blueColor];
self->view.frame = CGRectMake(200, 200, 80, 80);
} completion:^(BOOL finished) {

}];
/// 切换视图转场
// [UIView transitionFromView:view toView:view2 duration:3 options:UIViewAnimationOptionTransitionFlipFromBottom |UIViewAnimationOptionAllowAnimatedContent completion:^(BOOL finished) {
//
// }];
}

  切换视图的转场用于开发中整体视图的切换需求,这个操作会将原本的UIView从原本的父视图上移除,再将新的UIView视图添加到父视图上。其中option的定义是一个枚举,其中定义了许多转场的效果,可以进入源码查看其定义。

coreAnimation动画编程技术

  使用UIView层的过渡动画和转场动画可以满足绝大部分开发需求,但是也有许多局限性,使用CoreAnimation则可以更加自由的进行iOS动画编程。CoreAnimation是基于OpenGL和CoreGraphics图像处理框架的跨平台动画框架,下图给出了CoreAnimtion的系统结构图

coreAnimation

  CoreAnimation框架中大多数动画效果都是基于CALayer类实现的。每一个UIView都包含一个CALayer属性。边框,阴影等操作都是和CALayer相关的。

  锚点,所有的CALayer对象都有一个anchorPoint属性,这是CALayer对象的锚点,可以理解为对象动作的参照点,其中x,y都取值在0~1中,CALayer对象锚点值默认为(0,0)是视图的左上角,如果要设置为视图中心,则将锚点设置为(0.5,0.5),当CALayer层执行操作时都是以锚点为参考点进行的。比如一些平移,旋转等。抛开Layer层,view层主要是处理事件接受和用户交互的功能。

色彩梯度层–CAGradientLayer

  CAGradientLayer 是继承自CALayer的子类,是专门用来处理颜色梯度渐变的layer层

1
2
3
4
5
6
7
8
9
10
11
12
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = @[(id)[UIColor redColor].CGColor,(id)[UIColor blueColor].CGColor];
layer.bounds = CGRectMake(100, 100, 100, 100);
layer.position = CGPointMake(200, 200);
layer.locations = @[@0.2,@0.8];
layer.startPoint = CGPointMake(0, 0);
layer.endPoint = CGPointMake(1, 1);
[self.view.layer addSublayer:layer];
}

  上面代码中colors用于设置颜色梯度,bounds是设置CAlayer类的基础属性,用来设置layer层尺寸,position属性设置layer的位置,locations属性设置颜色梯度分界线,取值为0-1,startPoint,endPoint设置渲染的起始位置,此处为左上角到右下角,locations会渲染0.2部分红色,在渲染0.8部分的蓝色。

视图拷贝层–CARelicatorLayer

  CARelicatorLayer可以理解为layer层的一个复制容器,起作用是复制一个已经存在的layer层对象进行复制渲染.

1
2
3
4
5
6
7
8
9
10
11
12
///CARelicatorLayer
CALayer *baseLayer = [CALayer layer];
baseLayer.bounds = CGRectMake(0, 0, 100, 100);
baseLayer.position = CGPointMake(100, 400);
baseLayer.backgroundColor = [UIColor redColor].CGColor;
CAReplicatorLayer *reLayer = [CAReplicatorLayer layer];
reLayer.instanceRedOffset = -0.2;
reLayer.position = CGPointMake(0, 0);
reLayer.instanceTransform = CATransform3DMakeTranslation(100, 0, 0);
reLayer.instanceCount = 3;
[reLayer addSublayer:baseLayer];
[self.view.layer addSublayer:reLayer];

  此处layer先创建一个layer层作为要复制的原本,作为子layer加到CARelicatorLayer对象中,CARelicatorLayer的instanceRedOffset设置每个复制副本背景色中红色的偏移量,对应还有蓝色,绿色和alpha的偏移量。instanceTransform属性用于设置复制视图的3D变化属性,上面的代码表示将每个复制的layer层沿着x轴正方向偏移100个单位。

图形渲染层–CAShapeLayer

  CAShapeLayer是用于绘制图形的layer层,可以用来绘制一些自定义的图形。

1
2
3
4
5
6
7
8
9
/// CAShapeLayer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.position = CGPointMake(0, 0);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, 0, 300, 600);
CGPathAddLineToPoint(path, 0, 200, 500);
CGPathAddLineToPoint(path, 0, 100, 600);
shapeLayer.path = path;
[self.view.layer addSublayer:shapeLayer];

  上面的代码中CGMutablePathRef创建了iOS中的绘图路径对象,CGPathCreateMutable()方法创建了可变的绘图路径对象,CGPathMoveToPoint()方法设置绘图路径起点,CGPathAddLineToPoint()为绘图路径增加一条线。fillColor可以设置图像的填充色。还可以设置线条颜色,线条宽度等内容。

文本绘制层–CATextLayer

  CAtextLayer 用于进行视图上文本的绘制,UILable控件就是基于CATextLayer实现的。

1
2
3
4
5
6
7
8
9
/// CATextLayer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.bounds = CGRectMake(0, 0, 320, 100);
textLayer.position = CGPointMake(300, 700);
textLayer.string = @"这是一段文字";
textLayer.fontSize = 25;
textLayer.foregroundColor = [UIColor redColor].CGColor;
textLayer.alignmentMode = kCAAlignmentLeft;
[self.view.layer addSublayer:textLayer];

  上面代码中设置了一段在layer上显示的文字。

CAAnimation动画体系介绍

  通过前面的介绍,开发者可以通过CALayer设置许多视图UI上的属性,CAAnimation框架作用就是将这些属性的变化都以动画的形式展示出来,由于CALayer相对UIView可以更加灵活的设置更多属性,因此CALayer层的动画操作更自由。下图中是CAAnimation及其子类的继承关系结构图:

CAAnimation继承图

  CAAnimation分出的三个子类CAPropertyAnimation,CAAnimationGroup,CATranstion分别用于处理三种不同类型的动画.CAPropertyAnimation可以处理视图属性改变的动画。CAAnimationGroup可以处理组合动画,CATranstion可以处理转场动画。CAPropertyAnimation分出两个子类,CABasicAnimation和CAKeyframeAnimation,这两个子类分别用来处理基础类动画和关键帧动画。基础动画的一个子类CASpringAnimation处理阻尼动画。

  CAAnimation中定义了两个主要属性

1
2
CAMediaTimingFunction *timingFunction; //动画执行时序方式
BOOL removedOnCompletion; //动画完成时是否移除对象

   timingFunction中的动画效果是线性执行,淡入,淡出等,点击查看源码可以查看有哪些效果。

   CAPropertyAnimation定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13

@interface CAPropertyAnimation : CAAnimation
///初始化方法
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
/// 需要执行动画的属性名称,比如背景色渐变动画path可以设置为backgroundColor
@property(nullable, copy) NSString *keyPath;
/// 动画的执行状态是否叠加到视图的原状态上 if true, 第二次动画执行从第一次终点开始
@property(getter=isAdditive) BOOL additive;
/// 动画是否重复执行
@property(getter=isCumulative) BOOL cumulative;
/// 这个属性和transform动画执行相关
@property(nullable, strong) CAValueFunction *valueFunction;
@end
CABasicAnimation创建基础动画

  CABasicAnimation是动画框架中最常用的动画执行类,使用CABasicAnimation创建一个渐变色动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
/// gradient
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
anim.fromValue = (id)[UIColor redColor].CGColor;
anim.toValue = (id)[UIColor blueColor].CGColor;
anim.duration = 2;
[_layer addAnimation:anim forKey:@""];
/// rotate
CABasicAnimation *anima2 = [CABasicAnimation animationWithKeyPath:@"transform"];
anima2.fromValue = @0;
anima2.toValue = @200;
anima2.duration = 2;
anima2.valueFunction =[CAValueFunction functionWithName:kCAValueFunctionTranslateX];
[_layer addAnimation:anima2 forKey:@"2"];
}
CAKeyFrameAnimation创建关键帧动画

  通过关键帧创建的动画可以通过设置关键帧的方式自由控制整个动画的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
_layer = [CALayer layer];
_layer.bounds = CGRectMake(0, 0, 100, 100);
_layer.position = CGPointMake(160, 200);
_layer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:_layer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CAKeyframeAnimation *ani = [CAKeyframeAnimation animationWithKeyPath:@"position"];
ani.values = @[[NSValue valueWithCGPoint:CGPointMake(100, 200)],[NSValue valueWithCGPoint:CGPointMake(120, 300)],[NSValue valueWithCGPoint:CGPointMake(140, 400)],[NSValue valueWithCGPoint:CGPointMake(160, 800)]];
///设置每段动画的时间占比,第一个位置到第二个位置占比0.5,第二个位置到第三个位置占比0.3
ani.keyTimes = @[@0,@0.5,@0.8,@1];
ani.duration = 3;
[_layer addAnimation:ani forKey:@""];
}

  运行之后可以看到视图在三秒内进行了位置偏移,切速度逐渐变快

CATranstion–layer层的转场动画

  CATransition动画用来处理CAlayer层的转场效果,可以在CALayer对象出现时展示动画效果

1
2
3
4
5
6
7
8
9
10
11
12
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(100, 300);
layer.backgroundColor = [UIColor redColor].CGColor;
CATransition *ani = [CATransition animation];
ani.type = kCATransitionPush;
ani.subtype = kCATransitionFromRight;
ani.duration = 3;
[layer addAnimation:ani forKey:@""];
[self.view.layer addSublayer:layer];
}

   其中的type用来设置转场动画的类型,subType设置动画执行的方向。

layer层组合动画–CAAnimationGroup

  CAAnimationGroup类没有定义特定的动画类型,可以理解为是一个动画容器,在其中加入组合后的动画效果,进行展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
ani1.toValue = (id)[UIColor blueColor].CGColor;
CABasicAnimation *ani2 = [CABasicAnimation animationWithKeyPath:@"position"];
ani2.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 400)];
CAAnimationGroup *group = [CAAnimationGroup animation];
CABasicAnimation *ani3 = [CABasicAnimation animationWithKeyPath:@"transform"];
ani3.fromValue = @0;
ani3.toValue = @M_PI;
ani3.valueFunction =[CAValueFunction functionWithName:kCAValueFunctionRotateZ];
ani3.beginTime = 2;
group.duration = 3;
group.animations = @[ani1,ani2,ani3];
[_layer addAnimation:group forKey:@""];
}

  上述代码给layer增加了三层动画,同时设置了旋转动画从第2秒才开始执行。

CATransform3D动画

  任何CALayer对象都有transform属性,这个属性用来设置CALayer对象数学变换的效果。可以实现平移,旋转,放缩等动画效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIView *view = [UIView new];
view.frame = CGRectMake(100, 200, 100, 100);
view.backgroundColor = [UIColor redColor];
UIView *view2 = [[UIView alloc]initWithFrame:CGRectMake(100, 200, 100, 100)];
view2.backgroundColor = [UIColor blueColor];
/// x平移100 y平移100,z平移0
CATransform3D trans = CATransform3DTranslate(view2.layer.transform, 100, 100, 0);
/// x方向缩小0.5,y方向放大2
CATransform3D trans2 = CATransform3DScale(trans, 0.5, 2, 0);
///旋转变换沿着z轴旋转45度
CATransform3D trans3 = CATransform3DRotate(trans2, M_PI_4, 0, 0, 1);
view2.layer.transform = trans3;
/// 用于将要变换的效果进行翻转
view.layer.transform = CATransform3DInvert(trans2);
[self.view addSubview:view];
[self.view addSubview:view2];
}

  上面代码在同样位置创建了两个view,然后使用平移变换将其中一个进行了移动。trans2给图像增加了放缩变换,trans3给图像增加了z轴的旋转变换,CATransform3DInvert给将要增加的效果进行翻转。
  效果如下
CATransform3D

粒子动画效果

  宏观上有序的,轨迹确定的规则动画可以从过layer层和UIViewAnimation实现,对于微观的,无序的,轨迹无法预测的随机动画,可以使用CAEmitterLayer实现。CAEmitterLayer可以实现效果炫酷的粒子动画,粒子效果分为两部分,一部分是粒子发射器,用于配制粒子的整体效果,另一部分是粒子单元,用于配制粒子的具体属性。CAEmitterLayer继承子CALayer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
_emitterLayer = [[CAEmitterLayer alloc] init];
/// 粒子发射器在xy平面的位置
_emitterLayer.emitterPosition = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height - 20);
/// 粒子发射器的大小
_emitterLayer.emitterSize = CGSizeMake(self.view.frame.size.width-100, 20);
_emitterLayer.renderMode = kCAEmitterLayerAdditive;

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
view.backgroundColor = [UIColor whiteColor];
view.layer.cornerRadius = 5;
[self.view addSubview:view];
UIImage *img = [self imageFromView:view];
/// 火焰发射单元
/// 粒子单元
CAEmitterCell *fire = [CAEmitterCell emitterCell];
/// 粒子的创建速率 默认1/s
fire.birthRate = 800;
/// 粒子存活时间,默认1s
fire.lifetime = 3.0;
/// 粒子的生存时间容差
fire.lifetimeRange = 1.5;
fire.color = [UIColor colorWithRed:0.8 green:0.4 blue:0.2 alpha:0.1].CGColor;
fire.name = @"fire";
fire.contents = (id)img.CGImage;
/// 粒子速率
fire.velocity = 160;
/// 粒子速率容差
fire.velocityRange = 80;
/// 粒子在xy平面的发射角度
fire.emissionLongitude = M_PI + M_PI_2;
/// 粒子发射角度容差
fire.emissionRange = M_PI_2;
/// 粒子缩放速度
fire.scaleSpeed = 0.3;
/// 粒子旋转度
fire.spin = 0.2;
/// 烟雾
CAEmitterCell *smoke = [CAEmitterCell emitterCell];
smoke.birthRate = 800;
smoke.lifetime = 6.0;
smoke.lifetimeRange = 1.5;
smoke.color = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.05].CGColor;
smoke.name = @"smoke";
smoke.contents = (id)img.CGImage;
smoke.velocity = 250;
smoke.velocityRange = 100;
smoke.emissionLongitude = M_PI+M_PI_2;
smoke.emissionRange = M_PI_2;
_emitterLayer.emitterCells = [NSArray arrayWithObjects:smoke,fire, nil];
[self.view.layer addSublayer:_emitterLayer];
}

- (UIImage *)imageFromView:(UIView *)view {
CGSize size = view.frame.size;
UIGraphicsBeginImageContext(size);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

  上述注释中解释了CAEmitterLayer和CAEmitterCell的部分属性,可以点击相关源码查看详细的定义。 最终的效果如下所示:

fire

播放动态gif图

  由于许多应用在进行某些需求的动画处理时会使用播放GIF图的方式,因此把这一技术也归纳为iOS动画开发技术。一般有两种方式进行GIF图的播放,一种是将其渲染为UIWebView进行播放,一种是将GIF文件中存储的图片数据与图片信息获取出来,使用UIImageView的帧动画进行播放。

使用UIWebView进行GIF图动态播放

  GIF图渲染为UIWebView进行渲染具有简单,效能强的特点,缺点是加载GIF图需要一定的时间

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
///UIWebView已经废弃,使用WKWebView
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(100, 200, 280, 300)];
/// 注意gif资源不能放在Assests目录下
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"mygif" ofType:@"gif"]];
[webView loadData:data MIMEType:@"image/gif" characterEncodingName:@"UTF-8" baseURL:[NSURL fileURLWithPath:NSTemporaryDirectory()]];
[self.view addSubview:webView];
}
使用UIImageView帧动画播放gif图

   gif图是一组包含图片及其信息数据的文件,其中记录着各张图片播放时长等信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// 使用uiimageView播放gif图
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 500, 280, 300)];
[self.view addSubview:imgView];
NSString *dataPath = [[NSBundle mainBundle] pathForResource:@"mygif" ofType:@"gif"];
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:dataPath], NULL);
size_t count = CGImageSourceGetCount(source);
CGFloat allTime = 0;
NSMutableArray *imageArray = [[NSMutableArray alloc] init];
NSMutableArray *timeArray = [[NSMutableArray alloc] init];
NSMutableArray *heightArray = @[].mutableCopy;
NSMutableArray *widthArray = @[].mutableCopy;
for (size_t i = 0; i<count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
[imageArray addObject:(__bridge UIImage *)image];
CGImageRelease(image);
/// 图片信息
NSDictionary *info = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, i, NULL);
CGFloat width = [[info objectForKey:(__bridge NSString *)kCGImagePropertyPixelWidth] floatValue];
CGFloat height = [[info objectForKey:(__bridge NSString *)kCGImagePropertyPixelHeight] floatValue];
[widthArray addObject:[NSNumber numberWithFloat:width]];
[heightArray addObject:[NSNumber numberWithFloat:height]];
NSDictionary *timeDic = [info objectForKey:(__bridge NSDictionary *)kCGImagePropertyGIFDictionary];
CGFloat time = [[timeDic objectForKey:(__bridge NSString *)kCGImagePropertyGIFDelayTime] floatValue];
allTime +=time;
[timeArray addObject:[NSNumber numberWithFloat:time]];
}

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
NSMutableArray *times = @[].mutableCopy;
CGFloat currentTime = 0;
for (int i=0; i<imageArray.count; i++) {
[times addObject:[NSNumber numberWithFloat:currentTime/allTime]];
currentTime += [timeArray[i] floatValue];
}
[animation setKeyTimes:times];
[animation setValues:imageArray];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
animation.repeatCount = MAXFLOAT;
animation.duration = allTime;
[imgView.layer addAnimation:animation forKey:@"gifAnimation"];

  最后效果和前面的WkWebView相同,展示了一个gif图。不过代码比较繁琐。从中也可以看出,如果要对gif图中的某一部分进行操作也是可以通过这种方式实现的。

以上代码demo请参考这里

小游戏Flappy Bird设计

   flppy bird小游戏是对iOS动画的一个小应用,也是参考书中的一个小demo,其中小鸟的动作通过动画实现。需要抓住的点是背景的移动是通过定时器实现的,在1/60秒内,屏幕上的背景会向左移动一个像素,从而实现了背景的移动。给小鸟所在的屏幕增加一个点击事件,每次点击小鸟的坐标都可以变化一定的像素。通过随机数设置障碍物的位置。下面是两张截图: 源码请参考这里

flappybird1

flappybird2