一.UIView实现的动画
简单动画
Apple为UIView封装了一些很方便的API来使用动画:
+ animateWithDuration:animations:
+ animateWithDuration:delay:options:animations:completion:
这两个API会为UIView的几个属性补充过渡动画:
- frame
- bounds
- center
- transform
- alpha
- backgroundColor
- contentStretch
self.transform = CGAffineTransformMakeScale(1.3,1.3);
self.alpha =0;
[UIViewanimateWithDuration:0.35 animations:^{
self.alpha = 1;
self.transform = CGAffineTransformMakeScale(1,1);
}];
其中transform
对应的是图形图像的仿射变换,它用于位移/缩放/旋转。iOS 提供了下面的函数可以创建简单的 2D 变换:
CGAffineTransformMakeScale
CGAffineTransformMakeRotation
CGAffineTransformMakeTranslation
关键帧动画
上面介绍的动画中,我们只能控制开始和结束时的效果,然后由系统补全中间的过程,有些时候我们需要自己设定若干关键帧,实现更复杂的动画效果,这时候就需要关键帧动画的支持了。
[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat | UIViewKeyframeAnimationOptionAutoreverse animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
self.myView.frame = CGRectMake(10, 50, 100, 100);
}];
[UIView addKeyframeWithRelativeStartTime: 0.5 relativeDuration:0.3 animations:^{
self.myView.frame = CGRectMake(20, 100, 100, 100);
}];
[UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{
self.myView.transform = CGAffineTransformMakeScale(0.5, 0.5);
}];
} completion:nil];
这个例子添加了三个关键帧,在外面的animateKeyframesWithDuration
中我们设置了持续时间为 2.0 秒,这是真实意义上的时间,里面的startTime
和relativeDuration
都是相对时间。
以第一个为例,startTime
为 0.0,relativeTime
为 0.5,这个动画会直接开始,持续时间为 2.0 X 0.5 = 1.0 秒,下面第二个的开始时间是 0.5,正好承接上一个结束,第三个同理,这样三个动画就变成连续的动画了。
转换动画
+ transitionWithView:duration:options:animations:completion:
+ transitionFromView:toView:duration:options:completion:
这两个API用于控制在同一父视图下的两个子视图的转换动画。
二.CALayer实现的动画
UIView封装了常见的动画效果,如果想要更加细节的动画效果可以通过给UIView的Layer添加动画来实现。
通过导入QuartzCore.framework框架来使用。
基本动画(CABasicAnimation)
// 位移
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.duration = 0.8;
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
positionAnima.repeatCount = HUGE_VALF;
positionAnima.repeatDuration = 2;
positionAnima.removedOnCompletion = NO;
positionAnima.fillMode = kCAFillModeForwards;
[self.imageView.layer addAnimation:positionAnima forKey:@"AnimationMoveY"];
KeyPath
这里使用animationWithKeyPath
这个方法来改变 layer 的属性,如位移/缩放/旋转的属性。官方的KeyPath列表
具体参数
动画的参数和说明。
属性 | 说明 |
---|---|
duration | 动画时长 |
repeatCount | 动画重复次数,无限重复设置为 HUGE_VALF |
repeatDuration | 动画时间,在该时间内动画一直执行,不计次数 |
beginTime | 指定动画开始的时间。从开始延迟几秒的话,设置为【CACurrentMediaTime() + 秒数】 的方式 |
timingFunction | 设置动画的速度变化 |
autoreverses | 动画结束时是否执行逆动画 |
fromeValue | 所改变属性的起始值 |
toValue | 所改变属性的结束时的值 |
byValue | 所改变属性相同起始值的改变量 |
fromValue
,toValue
,byValue
这几个选项,支持的设置模式有下面几种:
- 设置 fromValue 和 toValue:从 fromValue 变化到 toValue
- 设置 fromValue 和 byValue:从 fromValue 变化到 fromValue + byValue
- 设置 byValue 和 toValue:从 toValue - byValue 变化到 toValue
- 设置 fromValue: 从 fromValue 变化到属性当前值
- 设置 toValue:从属性当前值变化到 toValue
- 设置 byValue:从属性当前值变化到属性当前值 + toValue
timingFunction
属性,使用这个属性可以自定义动画的运动曲线(节奏,pacing),系统提供了五种值可以选择:
- kCAMediaTimingFunctionLinear 线性动画
- kCAMediaTimingFunctionEaseIn 先快后慢
- kCAMediaTimingFunctionEaseOut 先慢后快
- kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢
- kCAMediaTimingFunctionDefault 默认,也属于中间比较快
此外,我们还可以使用 [CAMediaTimingFunction functionWithControlPoints] 方法来自定义运动曲线。
关键帧动画(CAKeyframeAnimation)
同 UIView 中的类似,CALayer 层也提供了关键帧动画的支持,CAKeyFrameAnimation 和 CABasicAnimation 都继承自 CAPropertyAnimation,因此它有具有上面提到的那些属性,此外,CAKeyFrameAnimation 还有特有的几个属性。
values 和 keyTimes
使用values
和keyTimes
可以共同确定一个动画的若干关键帧,示例代码如下:
CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];//在这里@"transform.rotation"==@"transform.rotation.z"
NSValue *value1 = [NSNumber numberWithFloat:-M_PI/180*4];
NSValue *value2 = [NSNumber numberWithFloat:M_PI/180*4];
NSValue *value3 = [NSNumber numberWithFloat:-M_PI/180*4];
anima.values = @[value1,value2,value3];
// anima.keyTimes = @[@0.0, @0.5, @1.0];
anima.repeatCount = MAXFLOAT;
[_demoView.layer addAnimation:anima forKey:@"shakeAnimation"];
可以看到上面这个动画共有三个关键帧,如果没有指定keyTimes
则各个关键帧会平分整个动画的时间(duration)。
path
使用 path 属性可以设置一个动画的运动路径,注意 path 只对 CALayer 的 anchorPoint 和position 属性起作用,另外如果你设置了 path ,那么 values 将被忽略。
CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(SCREEN_WIDTH/2-100, SCREEN_HEIGHT/2-100, 200, 200)];
anima.path = path.CGPath;
anima.duration = 2.0f;
[_demoView.layer addAnimation:anima forKey:@"pathAnimation"];
组动画(CAAnimationGroup)
组动画可以将一组动画组合在一起,所有动画对象可以同时运行,示例代码如下:
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animationOne.toValue = @2.0;
animationOne.duration = 1.0;
CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath:@"position.x"];
animationTwo.toValue = @400;
animationTwo.duration = 1.0;
[group setAnimations:@[animationOne, animationTwo]];
[self.myView.layer addAnimation:group forKey:nil];
需要注意的是,一个 group 组内的某个动画的持续时间(duration),如果超过了整个组的动画持续时间,那么多出的动画时间将不会被展示。例如一个 group 的持续时间是 5s,而组内一个动画持续时间为 10s ,那么这个 10s 的动画只会展示前 5s 。
切换动画(CATransition)
CATransition 可以用于 View 或 ViewController 直接的换场动画:
self.myView.backgroundColor = [UIColor blueColor];
CATransition *trans = [CATransition animation];
trans.duration = 1.0;
trans.type = @"push";
[self.myView.layer addAnimation:trans forKey:nil];
三.高级的动画效果
CADisplayLink(精确控制)
CADisplayLink 是一个计时器对象,可以周期性的调用某个 selecor 方法。相比 NSTimer ,它可以让我们以和屏幕刷新率同步的频率(每秒60次)来调用绘制函数,实现界面连续的不停重绘,从而实现动画效果。
iOS设备的屏幕刷新频率是固定的,CADisplayLink
在正常情况下会在每次刷新结束都被调用,精确度相当高。NSTimer
的精确度就显得低了点,比如NSTimer
的触发时间到的时候,runloop
如果在阻塞状态,触发时间就会推迟到下一个runloop
周期。并且 NSTimer
新增了tolerance
属性,让用户可以设置可以容忍的触发的时间的延迟范围。
CADisplayLink
使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer
的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用 CADisplayLink
比起用NSTimer
的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateTextColor)];
self.displayLink.paused = YES;
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
-(void)updateTextColor{
}
- (void)startAnimation{
self.beginTime = CACurrentMediaTime();
self.displayLink.paused = NO;
}
- (void)stopAnimation{
self.displayLink.paused = YES;
[self.displayLink invalidate];
self.displayLink = nil;
}
UIDynamicAnimator(物理效果)
UIDynamicAnimator 是 iOS 7 引入的一个新类,可以创建出具有物理仿真效果的动画,具体提供了下面几种物理仿真行为:
- UIGravityBehavior:重力行为
- UICollisionBehavior:碰撞行为
- UISnapBehavior:捕捉行为
- UIPushBehavior:推动行为
- UIAttachmentBehavior:附着行为
- UIDynamicItemBehavior:动力元素行为
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.myView]];
[self.animator addBehavior:gravityBehavior];
UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.myView]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collisionBehavior];
通过给视图添加上一个UIDynamicAnimator
实现iOS中动画的物理效果。
CAEmitterLayer(粒子效果)
CAEmitterLayer 是 Core Animation 提供的一个粒子发生器系统,可以用于创建各种粒子动画,例如烟雾,焰火等效果。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];

//create particle emitter layer
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:emitter];
//configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);
//create a particle template
CAEmitterCell *cell = [[CAEmitterCell alloc] init];
cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage;
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
//add particle template to emitter
emitter.emitterCells = @[cell];
}
@end