简介:
Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。
关于Core Animation完整的详细资料,这里有一份详细的介绍:http://blog.csdn.net/mad2man/article/details/16928891(由海水的味道翻译)
图层是Core Animation的核心,那么什么是CALayer
- OC中,基本上所有的控件都是
UIView
,而UIView
之所以能够在屏幕上显示,因为每一个UIView
内部都默认关联一个CALayer
. - 当
UIView
将要显示到屏幕上的时候,会调用drawRect:
进行绘图,将所有的内容绘制在自己的图层上,绘制完毕后,系统会将图层copy
到屏幕上.其实也就是说UIView本身不具备显示的功能,是因为有了Layer才有显示的功能,所以UIView和CALayer相互依赖
.
一、什么是隐式动画
-
Core Animation
基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在Core Animation
中手动打开,相反需要明确地关闭,否则他会一直存在。 - 所有手动创建的
CALayer
对象,都存在隐式动画,关于layer的属性,例如:
1.bounds
:用于设置CALayer
的宽高.修改属性会产生缩放动画
2.backgroundColor
:设置背景色,会产生背景色的渐变动画
3.position
:位置的改变,默认会产生平移动画
tips:以上都是隐式动画
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) CALayer *layer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 手动创建layer
self.layer = [CALayer layer];
self.layer.frame = CGRectMake(0, 0, 200, 200);
self.layer.backgroundColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:self.layer];
}
// 点击的时候更改layer的位置.(用于测试是否会产生隐式动画)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.layer.frame = CGRectMake(100, 100, 200, 200);
}
@end
那么如何关闭隐式动画呢?
CALayer隐式动画实际上是自动执行了CATransaction,CATransaction默认动画时间0.25s
// 点击的时候更改layer的位置.(用于测试是否会产生隐式动画)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.frame = CGRectMake(100, 100, 200, 200);
[CATransaction commit];
}
效果图:
将隐式动画时间改成
2s
,并改变layer
的背景颜色
- 通过
+ setAnimationDuration:
方法设置当前动画时间,或者通过+ animationDuration
方法来获取值(默认0.25秒)
[CATransaction begin];
// 改变动画时间:设置为2s
[CATransaction setAnimationDuration:2.0f];
[CATransaction setDisableActions:NO];
self.layer.frame = CGRectMake(100, 100, 200, 200);
self.layer.backgroundColor = [UIColor yellowColor].CGColor;
[CATransaction commit];
基于UIView的
block的动画
允许你在动画结束的时候提供一个完成的动作。CATranscation
接口提供的+ setCompletionBlock:
方法也有同样的功能
[CATransaction setCompletionBlock:^{
NSLog(@"完成了");
}];
[CATransaction commit];
NSLog(@"将要完成");
二、显式动画
- 隐式动画是在iOS平台创建动态用户界面的一种直接方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。
- 显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。
* 属性动画 - CAPropertyAnimation
属性动画作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。属性动画分为两种:基础和关键帧。
1. 基础动画 - CABasicAnimation
动画其实就是一段时间内发生的改变,最简单的形式就是从一个值改变到另一个值.
CABasicAnimation 继承 CAPropertyAnimation, 而CAPropertyAnimation是c所有动画类型的一个抽象基类
CAPropertyAnimation 通过指定动画的 KeyPath 作用于一个单一的属性.
类型的转换
id obj = @(float) // CGFloat 类型转换
id obj = [NSValue valueWithCGPoint:point] // CGPoint
id obj = [NSValue valueWithCGsize:point] // CGsize
id obj = [NSValue valueWithCGPoint:point] // CGPoint
id obj = [NSValue valueWithCGRect:point] // CGRect
CABasicAnimation 继承 CAPropertyAnimation,添加了如下属性:
@property(nullable, strong) id fromValue; // 动画开始前属性值
@property(nullable, strong) id toValue; // 动画结束后属性值
@property(nullable, strong) id byValue; // 动画过程中改变的值
/**
* tips: 这里的 id 类型 可以作用于不同的属性,比如:'数字','矢量','颜色','图片'
* 根据官方的头文件说明:这三个组合属性 可以有很多方式去组合.但是不能一次性同时指定'三个值',只需要指定'fromValue 和 toValue 或 byValue'
**/
- 根据官方的头文件说明:这三个组合属性 可以有很多方式去组合.但是不能一次性同时指定'三个值',只需要指定'fromValue 和 toValue 或 byValue'
-
用于CAPropertyAnimation的一些类型转换
*****基础动画的keyPath******
transform.rotation.x 围绕x轴翻转 参数:角度 angle2Radian(4)
transform.rotation.y 围绕y轴翻转 参数:同上
transform.rotation.z 围绕z轴翻转 参数:同上
transform.rotation 默认围绕z轴
transform.scale.x x方向缩放 参数:缩放比例 1.5
transform.scale.y y方向缩放 参数:同上
transform.scale.z z方向缩放 参数:同上
transform.scale 所有方向缩放 参数:同上
transform.translation.x x方向移动 参数:x轴上的坐标 100
transform.translation.y x方向移动 参数:y轴上的坐标
transform.translation.z x方向移动 参数:z轴上的坐标
transform.translation 移动 参数:移动到的点 (100,100)
opacity 透明度 参数:透明度 0.5
backgroundColor 背景颜色 参数:颜色 (id)[[UIColor redColor] CGColor]
cornerRadius 圆角 参数:圆角半径 5
borderWidth 边框宽度 参数:边框宽度 5
bounds 大小 参数:CGRect
contents 内容 参数:CGImage
contentsRect 可视内容 参数:CGRect 值是0~1之间的小数
hidden 是否隐藏
position
shadowColor
shadowOffset
shadowOpacity
shadowRadius
'通过CABasicAnimation来设置图层背景色'
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) CALayer *layer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.layer = [CALayer layer];
self.layer.frame = CGRectMake(50, 50, 200, 200);
self.layer.backgroundColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:self.layer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CABasicAnimation *animation = [CABasicAnimation animation];
animation.duration = 3.f;
' /* 效果发现:一旦动画结束并从图层上移除之后,layer就立刻恢复到之前定义的外观状态。我们从没改变过backgroundColor属性,所以图层就返回到原始的颜色。
* 如果需要动画完成后,layer的颜色不恢复到之前的外观,则需要加上下面这两行代码
*/
// animation.fromValue = (__bridge id _Nullable)(self.layer.backgroundColor);
// self.layer.backgroundColor = [UIColor redColor].CGColor;
'
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id _Nullable)([UIColor redColor].CGColor);
[self.layer addAnimation:animation forKey:nil];
}
@end
有小伙伴提出怎么在动画结束后保持效果,在这里感谢:"霸气侧漏"的这位蜀黍提供的代码;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
- CAAnimationDelegate
我们可以看到有两个方法.那么,优化上面的代码
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.backgroundColor =(__bridge CGColorRef)((CABasicAnimation *)anim).toValue;
[CATransaction commit];
}
- 关键帧动画 - CAKeyframeAnimation
CAKeyframeAnimation是另一种UIKit没有暴露出来但功能强大的类。和CABasicAnimation类似,CAKeyframeAnimation同样是CAPropertyAnimation的一个子类,它依然作用于单一的一个属性,但是和CABasicAnimation不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。
使用CAKeyframeAnimation 做颜色变化
@interface ViewController ()
@property (nonatomic,strong) CALayer *layer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.layer = [CALayer layer];
self.layer.frame = CGRectMake(50, 50, 200, 200);
self.layer.backgroundColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:self.layer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"backgroundColor";
animation.duration = 2.0f;
animation.values = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor orangeColor].CGColor,
(__bridge id)[UIColor yellowColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor cyanColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor purpleColor].CGColor];
[self.layer addAnimation:animation forKey:nil];
}
CAKeyframeAnimation
不能自动把当前值作为第一帧(类似1于1CABasicAnimation把fromValue
设为nil
),所以为了动画的平滑特性,我们需要开始和结束的关键帧来匹配当前属性的值
CAKeyframeAnimation有另一种方式去指定动画,就是使用CGPath.可以结合UIBezierpath创建路径,进行做动画
Tips:关于UIBezierPath,可以参考我上一篇
:iOS-UIBezierPath和各种layer把我玩坏了
- 根据UIBezierPath进行Animation
@interface ViewController ()
@property (nonatomic,strong) UIView *bezierPathView;
@property (nonatomic,strong) UIBezierPath *path;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.bezierPathView = [[UIView alloc] initWithFrame:(CGRectMake(10, 40, 80, 80))];
self.bezierPathView.layer.cornerRadius = 50;
self.bezierPathView.layer.masksToBounds = YES;
self.bezierPathView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:self.bezierPathView];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:(CGPointMake(40, 80))];
[path addLineToPoint:(CGPointMake(100, 300))];
[path addCurveToPoint:CGPointMake(300, 300) controlPoint1:(CGPointMake(200, 0)) controlPoint2:(CGPointMake(325, 500))];
self.path = path;
CAShapeLayer *shaperLayer = [CAShapeLayer layer];
shaperLayer.path = path.CGPath;
shaperLayer.fillColor = [UIColor clearColor].CGColor;
shaperLayer.strokeColor = [UIColor purpleColor].CGColor;
path.lineWidth = 2.0f;
[self.view.layer addSublayer:shaperLayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 5.0f;
animation.path = _path.CGPath;
// 通过rotation自动对齐图层到曲线
#warning 如果不适用自动对齐,效果不会太逼真.可以自己尝试效果
animation.rotationMode = kCAAnimationRotateAuto;
[self.bezierPathView.layer addAnimation:animation forKey:nil];
}
这样一个路径图就构造出来了,_
- 虚拟属性
- 属性动画实际上是针对关键路径而不是一个键,这就意味着可以对子属性甚至是虚拟属性进行做动画.
- 什么是虚拟属性?
如果想要对一个物体做旋转动画,应该用到transform属性,因为CALayer没有显示提供角度或者方向之类的属性
利用transform对layer做动画
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) UIView *animationView;
@property (nonatomic,strong) CALayer *layer;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.animationView = [[UIView alloc] initWithFrame:(CGRectMake(10, 40, 300, 300))];
self.animationView.backgroundColor = [UIColor cyanColor];
[self.view addSubview:self.animationView];
CALayer *layer= [CALayer layer];
layer.frame = CGRectMake(0, 0, 200, 200);
layer.position = CGPointMake(150, 150);
layer.contents = (__bridge id)[UIImage imageNamed:@"1.jpg"].CGImage;
[self.animationView.layer addSublayer:layer];
self.layer = layer;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CABasicAnimation *animtion = [CABasicAnimation animation];
animtion.keyPath = @"transform";
animtion.duration = 3.0f;
animtion.toValue = [NSValue valueWithCATransform3D:(CATransform3DMakeRotation(M_PI, 0, 0, 1))];
[self.layer addAnimation:animtion forKey:nil];
}
这里如果我们把M_PI(180°)调整到M_PI2(360°),就会发现怎么都不会动了.因为:toValue的结果是360°,又回到了最初的状态,所以值根本没有变,没有动画效果,那么如何改变呢?*
- *改变 : 对
transform.rotaion
进行动画 *
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CABasicAnimation *animtion = [CABasicAnimation animation];
animtion.keyPath = @"transform.rotation";
animtion.duration = 3.0f;
// animtion.toValue = [NSValue valueWithCATransform3D:(CATransform3DMakeRotation(M_PI * 2, 0, 0, 1))];
animtion.byValue = @(M_PI * 2);
[self.layer addAnimation:animtion forKey:nil];
}
为什么用
transform.rotation
?
- 我们可以不通过关键帧一次旋转多余180°的动画
- 可以用相对值 (byValue)
- 可以用不用CATransform3D
- 不会和transform.positon以及transform.scale冲突,完全分开进行独立的动画
- 组动画 - CAAnimationGroup
CAAnimationGroup可以把上面所述的动画类型进行组合在一起.
- CAAnimationGroup 也是继承于CAAnimtion的子类
- CAAnimationGroup 添加了
animaitons
属性
组合 CAKeyframeAnimtion
和 CABasicAnimation
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) UIView *animationView;
@property (nonatomic,strong) CALayer *layer;
@property (nonatomic,strong) CAAnimationGroup *group;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:(CGPointMake(50, 400))];
[path addCurveToPoint:(CGPointMake(300, 100)) controlPoint1:(CGPointMake(200, 0)) controlPoint2:CGPointMake(245, 350)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.lineWidth = 2.0f;
shapeLayer.strokeColor = [UIColor brownColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.path = path.CGPath;
[self.view.layer addSublayer:shapeLayer];
self.layer = [CALayer layer];
self.layer.frame = CGRectMake(0, 0, 80, 80);
self.layer.position =CGPointMake(100, 150);
self.layer.cornerRadius = 40;
self.layer.masksToBounds = YES;
self.layer.backgroundColor = (__bridge CGColorRef _Nullable)((__bridge id)[UIColor whiteColor].CGColor);
;
[self.view.layer addSublayer:self.layer];
CALayer *imgLayer = [CALayer layer];
imgLayer.frame = CGRectMake(0, 0, 50, 50);
imgLayer.position = CGPointMake(40, 40);
imgLayer.contents = (__bridge id)[UIImage imageNamed:@"1.jpg"].CGImage;
[self.layer addSublayer:imgLayer];
// position
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
keyAnimation.keyPath = @"position";
keyAnimation.path = path.CGPath;
keyAnimation.rotationMode = kCAAnimationRotateAuto;
// transform.rotation
CABasicAnimation *basicAnimaiton = [CABasicAnimation animation];
basicAnimaiton.keyPath = @"transform.rotation";
basicAnimaiton.byValue = @(M_PI * 2);
// opacity
CAKeyframeAnimation *alphaAnimation = [CAKeyframeAnimation animation];
alphaAnimation.keyPath = @"opacity";
alphaAnimation.values = @[@0.3,@0.5,@0.8,@0.1,@.4,@.9,@0.1,@.5,@.2,@.7,@.3,@1.0];
// backgroupColors
CAKeyframeAnimation *colorAnimtion = [CAKeyframeAnimation alloc];
colorAnimtion.keyPath = @"backgroundColor";
colorAnimtion.values = @[(__bridge id)[UIColor whiteColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor orangeColor].CGColor,
(__bridge id)[UIColor yellowColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor cyanColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor purpleColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor];
//groupAnimation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[keyAnimation,alphaAnimation,colorAnimtion,basicAnimaiton];
groupAnimation.duration = 5.0;
self.group = groupAnimation;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.layer addAnimation:self.group forKey:nil];
}
- 过渡动画 - CATransition
CATransition
同样继承于CAAnimation
- CATransition 在 API中已经有很清楚的说明了.他有一个
type
,subtype
来标记变换效果
简单的imageView加载图片
@interface ViewController ()
@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,strong) NSArray *imageArr;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithFrame:(CGRectMake(100, 100, 100, 100))];
[self.view addSubview:self.imageView];
self.imageView.image = [UIImage imageNamed:@"1.jpg"];
self.imageArr = @[[UIImage imageNamed:@"1.jpg"],
[UIImage imageNamed:@"2.jpg"],
[UIImage imageNamed:@"3.jpg"],
[UIImage imageNamed:@"4.jpg"]];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
[self.imageView.layer addAnimation:transition forKey:nil];
UIImage *current = self.imageView.image;
NSUInteger index = [self.imageArr indexOfObject:current];
index = (index + 1) % self.imageArr.count;
self.imageView.image = self.imageArr[index];
}
- 隐式过渡
当设置了CALayer的content
属性的时候,CATransition
是默认的行为。但对于UIView关联的图层,或者是其他隐式动画,这个特性依然是被禁用的,但是对于自己创建的Layer
,layer.contents
图片都会自动附上Fade
的动画。
- 使用UIKit提供的动画方法做过渡动画
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UIImage *current = self.imageView.image;
NSUInteger index = [self.imageArr indexOfObject:current];
index = (index + 1) % self.imageArr.count;
[UIView transitionWithView:self.imageView duration:1.0 options:(UIViewAnimationOptionTransitionFlipFromTop) animations:^{
self.imageView.image = self.imageArr[index];
} completion:NULL];
}
Tips: 最初的梦想,紧握在手上,让我们在最美的时光里,做最好的自己!