逐帧动画
前面介绍了核心动画中大部分动画类型,但是做过动画处理的朋友都知道,在动画制作中还有一种动画类型“逐帧动画”。说到逐帧动画相信很多朋友第一个想到的就是UIImageView,通过设置UIImageView的animationImages属性,然后调用它的startAnimating方法去播放这组图片。当然这种方法在某些场景下是可以达到逐帧的动画效果,但是它也存在着很大的性能问题,并且这种方法一旦设置完图片中间的过程就无法控制了。当然,也许有朋友会想到利用iOS的定时器NSTimer定时更新图片来达到逐帧动画的效果。这种方式确实可以解决UIImageView一次性加载大量图片的问题,而且让播放过程可控,唯一的缺点就是定时器方法调用有时可能会因为当前系统执行某种比较占用时间的任务造成动画连续性出现问题。
虽然在核心动画没有直接提供逐帧动画类型,但是却提供了用于完成逐帧动画的相关对象CADisplayLink。CADisplayLink是一个计时器,但是同NSTimer不同的是,CADisplayLink的刷新周期同屏幕完全一致。例如在iOS中屏幕刷新周期是60次/秒,CADisplayLink刷新周期同屏幕刷新一致也是60次/秒,这样一来使用它完成的逐帧动画(又称为“时钟动画”)完全感觉不到动画的停滞情况。
在iOS开篇“IOS开发系列--IOS程序开发概览”中就曾说过:iOS程序在运行后就进入一个消息循环中(这个消息循环称为“主运行循环”),整个程序相当于进入一个死循环中,始终等待用户输入。将CADisplayLink加入到主运行循环队列后,它的时钟周期就和主运行循环保持一致,而主运行循环周期就是屏幕刷新周期。在CADisplayLink加入到主运行循环队列后就会循环调用目标方法,在这个方法中更新视图内容就可以完成逐帧动画。
当然这里不得不强调的是逐帧动画性能势必较低,但是对于一些事物的运动又不得不选择使用逐帧动画,例如人的运动,这是一个高度复杂的运动,基本动画、关键帧动画是不可能解决的。所大家一定要注意在循环方法中尽可能的降低算法复杂度,同时保证循环过程中内存峰值尽可能低。下面以一个鱼的运动为例为大家演示一下逐帧动画。
#import "KCMainViewController.h"
#define IMAGE_COUNT 10
@interface KCMainViewController (){
CALayer *_layer;
int _index;
NSMutableArray *_images;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景
self.view.layer.contents=(id)[UIImage imageNamed:@"bg.png"].CGImage;
//创建图像显示图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 87, 32);
_layer.position=CGPointMake(160, 284);
[self.view.layer addSublayer:_layer];
//由于鱼的图片在循环中会不断创建,而10张鱼的照片相对都很小
//与其在循环中不断创建UIImage不如直接将10张图片缓存起来
_images=[NSMutableArray array];
for (int i=0; i<10; ++i) {
NSString *imageName=[NSString stringWithFormat:@"fish%i.png",i];
UIImage *image=[UIImage imageNamed:imageName];
[_images addObject:image];
}
//定义时钟对象
CADisplayLink *displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(step)];
//添加时钟对象到主运行循环
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
#pragma mark 每次屏幕刷新就会执行一次此方法(每秒接近60次)
-(void)step{
//定义一个变量记录执行次数
static int s=0;
//每秒执行6次
if (++s%10==0) {
UIImage *image=_images[_index];
_layer.contents=(id)image.CGImage;//更新图片
_index=(_index+1)%IMAGE_COUNT;
}
}
@end
运行效果:
注意:上面仅仅演示了逐帧动画的过程,事实上结合其他动画类型可以让整条鱼游动起来,这里不再赘述。
UIView动画封装
有了前面核心动画的知识,相信大家开发出一般的动画效果应该不在话下。在核心动画开篇也给大家说过,其实UIView本身对于基本动画和关键帧动画、转场动画都有相应的封装,在对动画细节没有特殊要求的情况下使用起来也要简单的多。可以说在日常开发中90%以上的情况使用UIView的动画封装方法都可以搞定,因此在熟悉了核心动画的原理之后还是有必要给大家简单介绍一下UIView中各类动画使用方法的。由于前面核心动画内容已经进行过详细介绍,学习UIView的封装方法根本是小菜一碟,这里对于一些细节就不再赘述了。
基础动画
基础动画部分和前面的基础动画演示相对应,演示点击屏幕落叶飘落到鼠标点击位置的过程。注意根据前面介绍的隐式动画知识其实非根图层直接设置终点位置不需要使用UIView的动画方法也可以实现动画效果,因此这里落花不再放到图层中而是放到了一个UIImageView中。
下面的代码演示了通过block和静态方法实现动画控制的过程:
#import "KCMainViewController.h"
@interface KCMainViewController (){
UIImageView *_imageView;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
//创建图像显示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
_imageView.center=CGPointMake(50, 150);
[self.view addSubview:_imageView];
}
#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
//方法1:block方式
/*开始动画,UIView的动画方法执行完后动画会停留在重点位置,而不需要进行任何特殊处理
duration:执行时间
delay:延迟时间
options:动画设置,例如自动恢复、匀速运动等
completion:动画完成回调方法
*/
// [UIView animateWithDuration:5.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
// _imageView.center=location;
// } completion:^(BOOL finished) {
// NSLog(@"Animation end.");
// }];
//方法2:静态方法
//开始动画
[UIView beginAnimations:@"KCBasicAnimation" context:nil];
[UIView setAnimationDuration:5.0];
//[UIView setAnimationDelay:1.0];//设置延迟
//[UIView setAnimationRepeatAutoreverses:NO];//是否回复
//[UIView setAnimationRepeatCount:10];//重复次数
//[UIView setAnimationStartDate:(NSDate *)];//设置动画开始运行的时间
//[UIView setAnimationDelegate:self];//设置代理
//[UIView setAnimationWillStartSelector:(SEL)];//设置动画开始运动的执行方法
//[UIView setAnimationDidStopSelector:(SEL)];//设置动画运行结束后的执行方法
_imageView.center=location;
//开始动画
[UIView commitAnimations];
}
@end
补充--弹簧动画效果
由于在iOS开发中弹性动画使用很普遍,所以在iOS7苹果官方直接提供了一个方法用于弹性动画开发,下面简单的演示一下:
#import "KCMainViewController.h"
@interface KCMainViewController (){
UIImageView *_imageView;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//创建图像显示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"ball.png"]];
_imageView.center=CGPointMake(160, 50);
[self.view addSubview:_imageView];
}
#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
/*创建弹性动画
damping:阻尼,范围0-1,阻尼越接近于0,弹性效果越明显
velocity:弹性复位的速度
*/
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
_imageView.center=location; //CGPointMake(160, 284);
} completion:nil];
}
@end
运行效果:
补充--动画设置参数
在动画方法中有一个option参数,UIViewAnimationOptions类型,它是一个枚举类型,动画参数分为三类,可以组合使用:
1.常规动画属性设置(可以同时选择多个进行设置)
UIViewAnimationOptionLayoutSubviews:动画过程中保证子视图跟随运动。
UIViewAnimationOptionAllowUserInteraction:动画过程中允许用户交互。
UIViewAnimationOptionBeginFromCurrentState:所有视图从当前状态开始运行。
UIViewAnimationOptionRepeat:重复运行动画。
UIViewAnimationOptionAutoreverse :动画运行到结束点后仍然以动画方式回到初始点。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套动画时间设置。
UIViewAnimationOptionOverrideInheritedCurve:忽略嵌套动画速度设置。
UIViewAnimationOptionAllowAnimatedContent:动画过程中重绘视图(注意仅仅适用于转场动画)。
UIViewAnimationOptionShowHideTransitionViews:视图切换时直接隐藏旧视图、显示新视图,而不是将旧视图从父视图移除(仅仅适用于转场动画)
UIViewAnimationOptionOverrideInheritedOptions :不继承父动画设置或动画类型。
2.动画速度控制(可从其中选择一个设置)
UIViewAnimationOptionCurveEaseInOut:动画先缓慢,然后逐渐加速。
UIViewAnimationOptionCurveEaseIn :动画逐渐变慢。
UIViewAnimationOptionCurveEaseOut:动画逐渐加速。
UIViewAnimationOptionCurveLinear :动画匀速执行,默认值。
3.转场类型(仅适用于转场动画设置,可以从中选择一个进行设置,基本动画、关键帧动画不需要设置)
UIViewAnimationOptionTransitionNone:没有转场动画效果。
UIViewAnimationOptionTransitionFlipFromLeft :从左侧翻转效果。
UIViewAnimationOptionTransitionFlipFromRight:从右侧翻转效果。
UIViewAnimationOptionTransitionCurlUp:向后翻页的动画过渡效果。
UIViewAnimationOptionTransitionCurlDown :向前翻页的动画过渡效果。
UIViewAnimationOptionTransitionCrossDissolve:旧视图溶解消失显示下一个新视图的效果。
UIViewAnimationOptionTransitionFlipFromTop :从上方翻转效果。
UIViewAnimationOptionTransitionFlipFromBottom:从底部翻转效果。
关键帧动画
从iOS7开始UIView动画中封装了关键帧动画,下面就来看一下如何使用UIView封装方法进行关键帧动画控制,这里实现前面关键帧动画部分对于落花的控制。
#import "KCMainViewController.h"
@interface KCMainViewController (){
UIImageView *_imageView;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
//创建图像显示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
_imageView.center=CGPointMake(50, 150);
[self.view addSubview:_imageView];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//UITouch *touch=touches.anyObject;
//CGPoint location= [touch locationInView:self.view];
/*关键帧动画
options:
*/
[UIView animateKeyframesWithDuration:5.0 delay:0 options: UIViewAnimationOptionCurveLinear| UIViewAnimationOptionCurveLinear animations:^{
//第二个关键帧(准确的说第一个关键帧是开始位置):从0秒开始持续50%的时间,也就是5.0*0.5=2.5秒
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
_imageView.center=CGPointMake(80.0, 220.0);
}];
//第三个关键帧,从0.5*5.0秒开始,持续5.0*0.25=1.25秒
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
_imageView.center=CGPointMake(45.0, 300.0);
}];
//第四个关键帧:从0.75*5.0秒开始,持所需5.0*0.25=1.25秒
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
_imageView.center=CGPointMake(55.0, 400.0);
}];
} completion:^(BOOL finished) {
NSLog(@"Animation end.");
}];
}
@end
补充--动画设置参数
对于关键帧动画也有一些动画参数设置options,UIViewKeyframeAnimationOptions类型,和上面基本动画参数设置有些差别,关键帧动画设置参数分为两类,可以组合使用:
1.常规动画属性设置(可以同时选择多个进行设置)
UIViewAnimationOptionLayoutSubviews:动画过程中保证子视图跟随运动。
UIViewAnimationOptionAllowUserInteraction:动画过程中允许用户交互。
UIViewAnimationOptionBeginFromCurrentState:所有视图从当前状态开始运行。
UIViewAnimationOptionRepeat:重复运行动画。
UIViewAnimationOptionAutoreverse :动画运行到结束点后仍然以动画方式回到初始点。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套动画时间设置。
UIViewAnimationOptionOverrideInheritedOptions :不继承父动画设置或动画类型。
2.动画模式设置(同前面关键帧动画动画模式一一对应,可以从其中选择一个进行设置)
UIViewKeyframeAnimationOptionCalculationModeLinear:连续运算模式。
UIViewKeyframeAnimationOptionCalculationModeDiscrete :离散运算模式。
UIViewKeyframeAnimationOptionCalculationModePaced:均匀执行运算模式。
UIViewKeyframeAnimationOptionCalculationModeCubic:平滑运算模式。
UIViewKeyframeAnimationOptionCalculationModeCubicPaced:平滑均匀运算模式。
注意:前面说过关键帧动画有两种形式,上面演示的是属性值关键帧动画,路径关键帧动画目前UIView还不支持。
转场动画
从iOS4.0开始,UIView直接封装了转场动画,使用起来同样很简单。
#import "KCMainViewController.h"
#define IMAGE_COUNT 5
@interface KCMainViewController (){
UIImageView *_imageView;
int _currentIndex;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//定义图片控件
_imageView=[[UIImageView alloc]init];
_imageView.frame=[UIScreen mainScreen].applicationFrame;
_imageView.contentMode=UIViewContentModeScaleAspectFit;
_imageView.image=[UIImage imageNamed:@"0.jpg"];//默认图片
[self.view addSubview:_imageView];
//添加手势
UISwipeGestureRecognizer *leftSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
leftSwipeGesture.direction=UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipeGesture];
UISwipeGestureRecognizer *rightSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
rightSwipeGesture.direction=UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipeGesture];
}
#pragma mark 向左滑动浏览下一张图片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:YES];
}
#pragma mark 向右滑动浏览上一张图片
-(void)rightSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:NO];
}
#pragma mark 转场动画
-(void)transitionAnimation:(BOOL)isNext{
UIViewAnimationOptions option;
if (isNext) {
option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromRight;
}else{
option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromLeft;
}
[UIView transitionWithView:_imageView duration:1.0 options:option animations:^{
_imageView.image=[self getImage:isNext];
} completion:nil];
}
#pragma mark 取得当前图片
-(UIImage *)getImage:(BOOL)isNext{
if (isNext) {
_currentIndex=(_currentIndex+1)%IMAGE_COUNT;
}else{
_currentIndex=(_currentIndex-1+IMAGE_COUNT)%IMAGE_COUNT;
}
NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
return [UIImage imageNamed:imageName];
}
@end
上面的转场动画演示中,其实仅仅有一个视图UIImageView做转场动画,每次转场通过切换UIImageView的内容而已。如果有两个完全不同的视图,并且每个视图布局都很复杂,此时要在这两个视图之间进行转场可以使用+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0)方法进行两个视图间的转场,需要注意的是默认情况下转出的视图会从父视图移除,转入后重新添加,可以通过UIViewAnimationOptionShowHideTransitionViews参数设置,设置此参数后转出的视图会隐藏(不会移除)转入后再显示。
注意:转场动画设置参数完全同基本动画参数设置;同直接使用转场动画不同的是使用UIView的装饰方法进行转场动画其动画效果较少,因为这里无法直接使用私有API。