1、核心动画的基本概念
Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。
Core Animation是跨平台的,可以用在Mac OS X和iOS平台。
Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。不阻塞主线程,可以理解为在执行动画的时候还能点击(按钮)。
CAAnimation 简介:
CAAimation
是所有动画对象的基类,负责控制动画的持续时间和速度,是个抽象类,不能直接使⽤用,应该使⽤用它具体的⼦子类
属性说明:
-
removedOnCompletion
// 动画是否⾃自动移出 -
duration
// 动画持续时间 -
speed
//速度 -
timeOffset
// 动画时间的偏移 -
repeatCount
// 动画重复执⾏行的次数(HUGE_VALF无限次) -
repeatDuration
// 动画重复执⾏行的总时间 -
autoreverses
// 反转动画 -
delegate
// 代理 -
fillMode
// 填充模式 -
timingFunction
//速度控制函数
基本属性说明:
属性 | 说明 |
---|---|
duration | 动画的持续时间 |
repeatCount | 重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT |
repeatDuration | 重复时间 |
removedOnCompletion | 默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置 |
fillMode | 决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之 |
beginTime | 可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间 |
timingFunction | 速度控制函数,控制动画运行的节奏 |
delegate | 动画代理 |
fillMode
属性的设置:
-
kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
-
kCAFillModeBackwards 在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。
-
kCAFillModeBoth 这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
速度控制函数(CAMediaTimingFunction):
-
kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
-
kCAMediaTimingFunctionEaseIn(加速):动画缓慢进入,然后加速离开
-
kCAMediaTimingFunctionEaseOut(减速):动画全速进入,然后减速的到达目的地
-
kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。
2、基本动画(CABasicAnimation)
CABasicAnimation 用于实现layer属性值从一个值(fromValue)到另外一个值(toValue)变化的简单动画,比如旋转、缩放、逐渐透明、移动等。
相关属性:
-
fromValue:keyPath相应属性的初始值
-
byValue:keyPath相应属性的中间值( 变化的值)
-
toValue:keyPath相应属性的结束值
动画过程说明:
-
随着动画的进⾏行,在⻓长度为duration的持续时间内,keyPath相应属性的值从
fromValue渐渐地变为toValue。
-
keyPath内容是CALayer的可动画Animatable属性
-
如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么
在动画执行完毕后,图层会保持显示动画执行后的状态。但实质上,图层属性值还是动画执行前的初始值,并没有真正被改变。
代码事例如下:
#import "ViewController.h"
@interface ViewController (){
CALayer *aniLayer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
aniLayer = [CALayer layer];
aniLayer.frame = CGRectMake(100, 50, 100, 100);
aniLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"足球"].CGImage);
[self.view.layer addSublayer:aniLayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 给layer 添加动画
[aniLayer addAnimation:[self positionAnimation] forKey:@"position"];
[aniLayer addAnimation:[self rotationAnimation] forKey:@"rotation"];
// [aniLayer addAnimation:[self boundsAnimation] forKey:@"bounds"];
// [aniLayer addAnimation:[self scaleAnimation] forKey:@"scale"];
}
- (CAAnimation *)positionAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 50)];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 550)];
animation.duration = 2.0;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.autoreverses = YES;
animation.repeatCount = HUGE_VALF; // HUGE_VALF 最大浮点数,表示无限次重复
/* 动画的线性变换(动画速度变化)
kCAMediaTimingFunctionLinear 匀速
kCAMediaTimingFunctionEaseIn 加速
kCAMediaTimingFunctionEaseOut 减速
kCAMediaTimingFunctionEaseInEaseOut 缓慢进入缓慢出去
kCAMediaTimingFunctionDefault 默认
*/
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
return animation;
}
- (CAAnimation *)boundsAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
animation.fromValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 100, 100)];
animation.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 300, 300)];
animation.duration = 2.0;
return animation;
}
- (CAAnimation *)rotationAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
// animation.fromValue = @0;
// animation.toValue = @(2 * M_PI);
animation.byValue = @( -2 * M_PI);
animation.duration = 2.0;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.autoreverses = YES;
animation.repeatCount = HUGE_VALF; // HUGE_VALF 最大浮点数,表示无限次重复
return animation;
}
- (CAAnimation *)scaleAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
// 1、初始值
animation.fromValue = @1.0;
// 2、目标值
animation.toValue = @2.0;
// 3、变化的值, fromValue ~ toValue 值的变化量
// animation.byValue = @1.0;
// 4、动画时间
animation.duration = 2.0;
/* 5、动画的填充模式:
kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved
*/
animation.fillMode = kCAFillModeForwards;
// 6、动画后是否移除动画后的状态(回到原始状态),默认是YES, 前提是要设置fillModle为:kCAFillModeForwards
animation.removedOnCompletion = NO;
// 7、是否有回复效果
animation.autoreverses = YES;
// 8、设置动画重复次数
animation.repeatCount = HUGE_VALF; // HUGE_VALF 最大浮点数,表示无限次重复
// 9、播放动画的速度
animation.speed = 2;
return animation;
}
@end
暂停动画:
-(void)stopAnimation{
// CACurrentMediaTime(): 当前媒体时间,表示系统启动后到当前的秒数,当系统重启后这个时间也重置
CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
// 设置动画的时间的偏移
layer.timeOffset = stopTime;
layer.speed = 0;
}
继续动画:
-(void)resumeAnimation{
// 获取暂停时的时间
CFTimeInterval stopTime = [layer timeOffset];
layer.speed = 1;
layer.timeOffset = 0;
layer.beginTime = 0;
// 设置开始的时间(继续动画,这样设置相当于让动画等待的秒数等于暂停的秒)
layer.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - stopTime;
}
代码说明:
-
设置的
animationWithKeyPath
是@"position",说明要修改的是CALayer的position属性,也就是会执行平移动画 -
animation.fromValue
,animation.toValue
这里的属性接收的时id类型的参数,因此并不能直接使用CGPoint这种结构体类型,而是要先包装成NSValue对象后再使用。 -
默认情况下,动画执行完毕后,动画会自动从CALayer上移除,CALayer又会回到原来的状态。为了保持动画执行后的状态,可以加入
animation.removedOnCompletion = NO
3、关键帧动画(CAKeyframeAnimation)
CAKeyframeAnimation 可以给一个图层提供多个目标值(values)或者一个指定路径(path)的动画。关键帧动画有如下几个重要参数:
values
:指定图层属性(position、scale、rotation...)的多个目标值,这个图层就会由这些指定的值进行动画。
path
:一个CGPathRef
类型的路径,指定图层就会沿着这个路径进行动画。
keyTimes
:关键帧指定对应的时间点,其取值范围为0到1.0。也就是keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间平分。
代码事例如下:
#import "ViewController.h"
#define TScreenWidth [UIScreen mainScreen].bounds.size.width
#define TScreenHeight [UIScreen mainScreen].bounds.size.height
@interface ViewController (){
CALayer *aniLayer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *bgLayer = [CALayer layer];
bgLayer.frame = self.view.bounds;
bgLayer.backgroundColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:bgLayer];
bgLayer.delegate = self;
// 重绘
[bgLayer setNeedsDisplay];
aniLayer = [CALayer layer];
aniLayer.bounds = CGRectMake(0 , 0, 100, 100);
aniLayer.position = CGPointMake(TScreenWidth / 2, 50);
aniLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"足球"].CGImage);
[self.view.layer addSublayer:aniLayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[aniLayer addAnimation:[self keyframeAnimation] forKey:@"keyAnimation"];
}
- (CAAnimation *)keyframeAnimation {
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyAnimation.duration = 4;
// keyAnimation.autoreverses = YES;
keyAnimation.repeatCount = HUGE_VALF;
keyAnimation.fillMode = kCAFillModeForwards;
keyAnimation.removedOnCompletion = NO;
/*
NSValue *point_1 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth / 2,0)];
NSValue *point_2 = [NSValue valueWithCGPoint:CGPointMake(50,TScreenHeight / 2)];
NSValue *point_3 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth / 2,TScreenHeight - 50)];
NSValue *point_4 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth - 50,TScreenHeight / 2)];
NSValue *point_5 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth / 2,0)];
// values:设置关键帧(多个目标点)
keyAnimation.values = @[point_1,point_2,point_3,point_4,point_5];
// 设置每一帧所在的时间比例
keyAnimation.keyTimes = @[@0, @0.2, @0.5, @0.6,@1.0];
*/
/* 插值计算模式:
kCAAnimationLinear 关键帧之间进行插值计算(线性的)
kCAAnimationDiscrete 关键帧之间不进行插值计算(离散的)
kCAAnimationPaced 关键帧之间匀速切换,keyTimes\timingFunctions的设置将不起作用
kCAAnimationCubic 关键帧进行圆滑曲线相连后插值计算
kCAAnimationCubicPaced 匀速并且关键帧进行圆滑曲线相连后插值计算
*/
keyAnimation.calculationMode = kCAAnimationLinear;
keyAnimation.path = [self path].CGPath;
return keyAnimation;
}
// 绘制路径
- (UIBezierPath *)path {
// 椭圆
// UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:self.view.bounds];
// 圆角矩形
// UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds cornerRadius:50];
// 内切圆
// UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 100, 300, 300)];
// 贝塞尔曲线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, TScreenHeight)];
CGPoint point_1 = CGPointMake(TScreenWidth, TScreenHeight);
CGPoint controPoint_1 = CGPointMake(TScreenWidth / 2, - TScreenHeight);
// CGPoint controPoint_2 = CGPointMake(TScreenWidth / 4 * 3, TScreenHeight);
[path addQuadCurveToPoint:point_1 controlPoint:controPoint_1];
// [path addCurveToPoint:point_1 controlPoint1:controPoint_1 controlPoint2:controPoint_2];
return path;
}
// 绘图
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextAddPath(ctx , [self path].CGPath);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 5);
CGContextDrawPath(ctx, kCGPathStroke);
}
@end
-
CAKeyframeAnimation——计算模式属性
-
在关键帧动画中还有一个非常重要的参数,那便是calculationMode,所谓计算模式:其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint和 position进行的动画
-
当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算
-
calculationMode目前提供如下几种模式:
kCAAnimationLinear: 默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算
kCAAnimationDiscrete: 离散的,不进行插值计算,所有关键帧直接逐个进行显示
kCAAnimationPaced :使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效
kCAAnimationCubic: 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑
kCAAnimationCubicPaced :看这个名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的
4、转场动画(CATransition)
-
CATransition是CAAnimation的子类,用于做转场动画
-
能够为图层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点
如:UINavigationController导航控制器就是通过CATransition转场动画实现了将控制器的视图推入屏 幕的动画效果
转场动画就是从一个场景以动画的形式过渡到另一个场景。转场动画的使用一般分为以下几个步骤:
1.创建转场动画 CATransition
2.设置转场类型transtion.type、子类型transtion.subtype(可选)及其他属性
3.设置转场后的新视图并添加动画到图层
下表列出了常用的转场类型(注意私有API是苹果官方没有公开的动画类型,但是目前通过仍然可以使用):
- 公开的API
fade 淡出效果 kCATransitionFade
movein 新视图移动到旧视图上 kCATransitionMoveIn
push 新视图退出旧视图上 kCATransitionPush
reveal 移开旧视图显示新视图 kCATransitionReveal
- 私有的API
cube 立体翻转效果
oglFlip 翻转效果
suckEffect 收缩效果
rippleEffect 水滴波纹效果
pageCurl 向上翻页效果
pageUnCurl 向下翻页效果
cameralIrisHollowOpen 摄像头打开效果
cameraIrisHollowClose 摄像头关闭效果
代码事例如下:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static int index = 1;
[self.imageView.layer addAnimation:[self transitionAnimation] forKey:nil];
NSString *imageName = [NSString stringWithFormat:@"large_%d.jpg",index];
self.imageView.image = [UIImage imageNamed:imageName];
index++;
if (index > 10) {
index = 0;
}
}
- (CAAnimation *)transitionAnimation {
CATransition *transitionAni = [CATransition animation];
transitionAni.duration = 1.0;
/*
1. fade 淡出效果
2. moveIn 进入效果
3. push 推出效果
4. reveal 移出效果
// 未公开的
5. cube 立方体翻转效果
6. suckEffect 抽走效果
7. rippleEffect 水波效果
8. pageCurl 翻开页效果
9. pageUnCurl 关闭页效果
10. cameraIrisHollowOpen 相机镜头打开效果
11. cameraIrisHollowClose 相机镜头关闭效果
*/
transitionAni.type = kCATransitionPush;
// transitionAni.type = @"push";
// 转场的方向:`fromLeft', `fromRight', `fromTop' `fromBottom'
transitionAni.subtype = @"fromTop";
// 开始转场和结束转场的进度位置
// transitionAni.startProgress = 0.5;
// transitionAni.endProgress = 1;
return transitionAni;
}
@end
5、动画组(CAAnimationGrup)
CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。
UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果。
- 相关属性: 1.type:动画过渡类型
2.subtype:动画过渡⽅方向
3.startProgress:动画起点
4.endProgress:动画终点
- type属性说明:
1.kCATransitionFade:淡⼊入淡出
2.kCATransitionMoveIn:新视图移到旧视图上⾯面
3.kCATransitionPush:新视图把旧视图推出
4.kCATransitionReveal:把旧视图移开,显⽰示下⾯面的新视图
代码事例如下:
#import "ViewController.h"
#define TScreenWidth [UIScreen mainScreen].bounds.size.width
#define TScreenHeight [UIScreen mainScreen].bounds.size.height
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *bgView;
@property (weak, nonatomic) IBOutlet UIImageView *aniImageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.bgView.layer.delegate = self;
[self.bgView.layer setNeedsDisplay];
NSLog(@"%@",NSStringFromCGRect(self.aniImageView.frame));
NSLog(@"%f, %f",TScreenWidth, TScreenHeight);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.aniImageView.layer addAnimation:[self animationGroup] forKey:@"group"];
}
- (CAAnimation *)positionAnimation {
CAKeyframeAnimation *keyAni = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyAni.duration = 3.0;
keyAni.fillMode = kCAFillModeForwards;
keyAni.removedOnCompletion = NO;
keyAni.path = [self path].CGPath;
return keyAni;
}
- (CAAnimation *)rotationAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.beginTime = 3.0;
animation.duration = 2.0;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.byValue = @(2 * M_PI);
return animation;
}
- (CAAnimation *)downAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.beginTime = 3.0;
animation.duration = 2.0;
animation.byValue = [NSValue valueWithCGPoint:CGPointMake(0, TScreenHeight)];
return animation;
}
- (UIBezierPath *)path {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, TScreenHeight)];
CGPoint toPoint = CGPointMake(TScreenWidth, 0);
CGPoint controlPoint = CGPointMake(TScreenWidth,TScreenHeight);
[path addQuadCurveToPoint:toPoint controlPoint:controlPoint];
return path;
}
- (CAAnimation *)animationGroup {
// 创建一个动画组,用于组合多种动画
CAAnimationGroup * aniGroup = [CAAnimationGroup animation];
// 动画组的完成时间
aniGroup.duration = 5.0;
aniGroup.fillMode = kCAFillModeForwards;
aniGroup.removedOnCompletion = NO;
// 组合多个动画
aniGroup.animations = @[[self positionAnimation],[self rotationAnimation],[self downAnimation]];
return aniGroup;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextAddPath(ctx, [self path].CGPath);
CGContextSetLineWidth(ctx, 5);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextDrawPath(ctx, kCGPathStroke);
}
@end