Core Animation 实际是直接作用在 CALayer 上的,而不是UIView上。view是负责响应事件的,layer是负责显示的,可以看成view是layer的包装类。同时Core Animation 的动画执行过程都是在后台操作的,不会阻塞主线程。
CAAnimation是所有动画对象的父类,负责控制动画的持续时间和速度,是个 抽象类 ---- 即,不能直接使用,应该使用其具体子类。需要注意的是CAAnimation 和 CAPropertyAnimation 都是 抽象类。
首先,我们先看一下继承结构图:
CAAnimation(动画基类)
CAAnimation主要包含三个属性,两个代理方法和遵循的CAMediaTiming协议。
1、属性:
-
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
:时间函数用来定义动画的节奏(速度控制函数)。 默认为nil,表示线性运动。CAMediaTimingFunction类定义了速度控制函数,共有四个函数:-
+ (instancetype)functionWithName:(NSString *)name;
参数选项共有五个:-
CA_EXTERN NSString * const kCAMediaTimingFunctionLinear
:线性匀速运动。 -
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseIn
:动画开始时会较慢,之后动画会加速。 -
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseOut
:动画在开始时会较快,之后动画速度减慢。 -
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseInEaseOut
:动画在开始和结束时速度较慢,中间时间段内速度较快。 -
CA_EXTERN NSString * const kCAMediaTimingFunctionDefault
:和kCAMediaTimingFunctionEaseInEaseOut很类似,但是加速和减速的过程都稍微有些慢。
-
-
+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
创建一个三次贝塞尔曲线来描述运动速度状态的改变, 曲线的终点为(0,0)和(1,1),由类实例定义的两个点'c1'和'c2'是控制点。 因此,定义贝塞尔曲线的点是:'[(0,0), c1, c2, (1,1)]'即'[(0,0), (c1x,c1y), (c2x,c2y), (1,1)]' -
- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
同上。 -
- (void)getControlPointAtIndex:(size_t)idx values:(float[2])ptr;
可以根据已知的CAMediaTimingFunction曲线,获知其控制点。
-
@property(nullable, strong) id <CAAnimationDelegate> delegate;
需要注意的是,这里是强引用,该对象在动画对象的生命周期中被保留。 默认为nil。@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
该属性控制当动画活动持续时间过去后,动画是否从渲染树中删除,即图像恢复原状。默认是YES。一般不要设置为NO,而是通过直接更改中心点方法去进行操作图像动画结束后状态。
2、代理方法:
- (void)animationDidStart:(CAAnimation *)aim;
动画已近开始后调用。- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
动画已经结束后调用,如果动画结束后没有被删除则flag为YES。即当removedOnCompletion
属性为YES时flag为NO。反之则为YES。
3、CAMediaTiming协议
CAMediaTiming协议通过layer和animations实现,它对一个分层的定时系统进行建模,每个对象描述从对象的父对象到自己本身本地时间的时间值的映射(有点拗口~)。
马赫时间被定义为转换为秒的绝对时间,系统提供了CACurrentMediaTime()
函数来实现此功能,方便查询当前的绝对时间(即从设备启动后经历的时间的秒数,其实没有实际意义,可以用来测试代码效率,在这里作为参照时间使用,不需要管实际值是多少)。
该协议共包括8个属性:
@property CFTimeInterval beginTime;
指定动画开始的时间(相对于其父对象),默认为0。从开始延迟几秒的话,设置为CACurrentMediaTime() + 秒数
的方式。@property CFTimeInterval duration;
动画持续时间,默认为0。@property float speed;
layer层动画速度,默认为1, 用于将父对象时间缩放到当地时间。例如如果速率为2,duration为3秒,则其实1.5秒后动画就完成了。同样该属性值可以叠乘,因为CALayer也实现了相同的协议,这就意味着你可以设置layer的speed为2.0,这样,所有加入到layer的动画运行都要快两倍。若设置此时设置animation的speed为2,则会比原来速度快4倍!@property CFTimeInterval timeOffset;
动画当地时间当地的额外偏移量。 范围是[0 - duration],是指从中间某一状态开始,然后将前面的部分补充到后面。一个用途是通过将“speed”设置为0并将“offset”设置为合适的值来“暂停”一层。 默认为0。@property float repeatCount;
动画重复次数。默认为0。@property CFTimeInterval repeatDuration;
动画重复持续时间,默认为0。例如,duration为2.0,repeatCount为2,那么动画完整持续时间应该是4s,而且动画也会按照4s的进度进行,此时如果设置repeatDuration为2.0,则实际效果看起来动画只运行了一遍没有重复进行。@property BOOL autoreverses;
是否自动执行动画在向前播放后反向播放。默认为NO。-
@property(copy) NSString *fillMode;
该属性定义了定时对象在其活动持续时间之外的行为。系统提供了四个常量:CA_EXTERN NSString * const kCAFillModeForwards
:动画向前延伸,所谓向前即向动画持续的方向,在超出持续时间后,如果removedOnCompletion设置为NO则会保持动画最后的状态,直到手动调用removeAnimation方法。CA_EXTERN NSString * const kCAFillModeBackwards
:
将会立即执行动画的第一帧,不论是否设置了 beginTime属性,直到 beginTime 时开始正常动画。CA_EXTERN NSString * const kCAFillModeBoth
:前两者的集合。CA_EXTERN NSString * const kCAFillModeRemoved
:默认情况,动画完成后直接移除。
以上属性均可组合使用,以达到理想的效果。关于以上属性间的关系可以参考一下下图(关系图),这里参考了 大卫·罗恩奎斯特 的 Controlling Animation Timing。这篇文章,在此表示非常的感谢!
一、CAPropertyAnimation (属性动画)
CAPropertyAnimation是基于属性的动画子类,它与CAAnimation一样是一个抽象类 --- 不能直接使用,应该使用其具体子类。共包含一个创建方法和四个属性:
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
根据路径创建对象,关于path可以参考该文章中 表2 部分@property(nullable, copy) NSString *keyPath;
即上面的path。@property(getter=isAdditive) BOOL additive;
如何处理多个动画在同一时间段执行的结果,若为YES,同一时间段的动画合成为一个动画,默认为NO。(使用 CAKeyframeAnimation 时必须将该属性指定为 YES ,否则不会出现期待的结果)@property(getter=isCumulative) BOOL cumulative
影响重复动画如何产生结果,若为YES,则动画的当前值是上一个重复周期结束时的值,加上当前重复周期的值。 如果为NO,则该值只是为当前重复周期计算的值。 默认为NO。-
@property(nullable, strong) CAValueFunction *valueFunction;
默认为nil。该属性值是一个CAValueFunction对象,该对象负责对属性改变的插值计算,系统已经提供了默认的插值计算方式,因此一般无须指定该属性。- 关于CAValueFunction对象:
+ (nullable instancetype)functionWithName:(NSString *)name;
@property(readonly) NSString *name;
其中参数name由系统提供:
CA_EXTERN NSString * const kCAValueFunctionRotateX
CA_EXTERN NSString * const kCAValueFunctionRotateY
CA_EXTERN NSString * const kCAValueFunctionRotateZ
CA_EXTERN NSString * const kCAValueFunctionScale
CA_EXTERN NSString * const kCAValueFunctionScaleX
CA_EXTERN NSString * const kCAValueFunctionScaleY
CA_EXTERN NSString * const kCAValueFunctionScaleZ
CA_EXTERN NSString * const kCAValueFunctionTranslate
CA_EXTERN NSString * const kCAValueFunctionTranslateX
CA_EXTERN NSString * const kCAValueFunctionTranslateY
CA_EXTERN NSString * const kCAValueFunctionTranslateZ
1、CABaseAnimation(基础动画)
CABaseAnimation用来实现一些比较简单的动画比如平移、缩放、旋转等,它本身只有三个属性,而且如果使用该动画,则至多有两个值不为nil。
-
@property(nullable, strong) id fromValue;
动画从哪个值开始,若为nil,则从属性当前值开始动画。若只有该属性不为nil,则动画在属性的fromValue和当前值之间进行! -
@property(nullable, strong) id toValue;
动画到哪个值结束。 -
@property(nullable, strong) id byValue;
动画通过那个值。若只有该属性不为nil,则产生效果为 :toValue
=原属性值
+byValue
,然后按照这一目的值产生动画。若只有fromValue和byValue不为nil,怎相当于在fromValue
和(fromValue+ byValue
)之间产生动画。若只有toValue和byValue不为nil,则在(toValue-byValue
)和toValue
之间产生动画。若三者同时存在byValue不起作用!!!
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CABasicAnimation * baseAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
baseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
baseAnimation.duration = 5.0;
// baseAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(300, 500)];
// [self.redView.layer addAnimation:baseAnimation forKey:@"onlyFromValue"];
// baseAnimation.byValue = [NSValue valueWithCGPoint:CGPointMake(300, 500)];
// [self.redView.layer addAnimation:baseAnimation forKey:@"onlyByValue"];
// baseAnimation.byValue = [NSValue valueWithCGPoint:CGPointMake(100, 175)];
// baseAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 500)];
// [self.redView.layer addAnimation:baseAnimation forKey:@"byValue-toValue"];
baseAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(300, 200)];
baseAnimation.byValue = [NSValue valueWithCGPoint:CGPointMake(100, 175)];
baseAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 500)];
[self.redView.layer addAnimation:baseAnimation forKey:@"fromValue-byValue-toValue"];
}
2、CAKeyFrameAnimation(关键帧动画)
关键帧动画的值可以指定一个value数组或者一个路径(CGPathRef)。和CABasicAnimation动画相比,在同一时间内对同一layer可以做多种不同动画,并且可以控制各动画的执行节奏。
-
@property(nullable, copy) NSArray *values;
: 相当于CABasicAnimation中只有的toValue情况,不过这里是一个toValue的集合。 -
@property(nullable) CGPathRef path;
:定义动画功能行为的可选路径对象。 当值非nil时,会覆盖values
属性。 除了moveto
点(起点)之外,路径中的每个点都定义了一个关键帧,用于定时和插值。 默认为nil。 需要注意的是对于沿着路径的恒速动画,calculateMode
应该设置为paced
而不是linear
。 -
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;
:关键帧动画每帧动画开始执行时间点的数组,取值范围为0~1,即,将duration
属性单位化。一般情况下数组中相邻两个值必须遵循后一个值大于或等于前一个值,并且最后的值不能为大于1。未设置时默认每帧动画执行时间均分。 -
@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions;
:CAMediaTimingFunction对象的可选数组。 如果values
数组定义了n个关键帧,则在timingFunctions
数组中应该有n-1个对象。 -
@property(copy) NSString *calculationMode;
:“计算模式”。 可能的值为“discrete”,“linear”,“paced”,“cubic”和“cubicPaced”。 默认为“linear”。 当设置为“paced”或“cubicPaced”时,动画的keyTimes
和timingFunctions
属性将被忽略并隐式计算。也可以用系统提供的常量参数:CA_EXTERN NSString * const kCAAnimationLinear
CA_EXTERN NSString * const kCAAnimationDiscrete
CA_EXTERN NSString * const kCAAnimationPaced
CA_EXTERN NSString * const kCAAnimationCubic
CA_EXTERN NSString * const kCAAnimationCubicPaced
-
@property(nullable, copy) NSArray<NSNumber *> *tensionValues;
张力值、@property(nullable, copy) NSArray<NSNumber *> *continuityValues;
连续性值、@property(nullable, copy) NSArray<NSNumber *> *biasValues;
偏置值。这些定义都是在三次曲线计算模式(cubic calculation modes)情况下才能起作用,对应每一个控制点,取值范围[-1, 1]。张力值控制曲线的“紧度”(正值更紧,负值更圆)。 连续性值控制段如何连接(正值给出锐角,负值给出倒角)。 偏置值定义曲线出现位置(正值在控制点前移动曲线,负值在控制点后移动)。 -
@property(nullable, copy) NSString *rotationMode;
:定义沿着路径y运行体是否自动旋转调整以使自身横截面匹配路径切线,默认为nil。(只有设置了path
才有意义)。-
CA_EXTERN NSString * const kCAAnimationRotateAuto
自动匹配。 -
CA_EXTERN NSString * const kCAAnimationRotateAutoReverse
在上一常量的基础上顺时针多旋转180°。
-
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CAKeyframeAnimation * keyFrameAni = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyFrameAni.duration = 10.0;
keyFrameAni.values = @[[NSValue valueWithCGPoint:CGPointMake(50, 100)], [NSValue valueWithCGPoint:CGPointMake(300, 100)], [NSValue valueWithCGPoint:CGPointMake(300, 500)], [NSValue valueWithCGPoint:CGPointMake(50, 500)],[NSValue valueWithCGPoint:CGPointMake(50, 100)]];
keyFrameAni.keyTimes = @[[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.3], [NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:1.0]];
keyFrameAni.timingFunctions =@[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
if (!isPath) {
keyFrameAni.path = nil;
keyFrameAni.rotationMode = nil;
isPath = YES;
}else{
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 100, 250, 400) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(125, 125)];
keyFrameAni.path = path.CGPath;
keyFrameAni.calculationMode = kCAAnimationPaced;
keyFrameAni.rotationMode = kCAAnimationRotateAuto;
// keyFrameAni.rotationMode = kCAAnimationRotateAutoReverse;
isPath = NO;
}
[self.imageView.layer addAnimation:keyFrameAni forKey:@"noPath"];
}
看一下简单的效果:
3、CASpringAnimation(弹性动画)
弹性动画父类是CABasicAnimation,主要用来设置类似于缓冲、减速动画。
-
@property CGFloat mass;
:质量,影响图层运动时的弹簧惯性,质量越大,惯性越大,弹簧拉伸和压缩的幅度越大。该值必须大于0,默认为1。 -
@property CGFloat stiffness;
:弹簧刚度系数,刚度系数越大,形变产生的力就越大,弹性运动越快,必须大于0,默认为100。 -
@property CGFloat damping;
:阻尼/减幅,阻止弹簧伸缩的系数,阻尼系数越大,停止越快(可以看成阻力~),必须大于等于0,默认是10。 -
@property CGFloat initialVelocity;
:附着在弹簧上的物体的初始速度。 默认为零。速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向会向与运动方向相反的方向先做运动。 -
@property(readonly) CFTimeInterval settlingDuration;
:返回估计的持续时间。注意该属性是只读属性, 针对当前动画参数评估持续时间。一般动画使用该时间作为持续时间也是比较好的。
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.redView = [UIView new];
self.redView.frame = CGRectMake(175, 175, 100, 100);
self.redView.center = self.view.center;
self.redView.backgroundColor = [UIColor redColor];
self.redView.alpha = 0.5;
[self.view addSubview:self.redView];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CASpringAnimation * springAni = [CASpringAnimation animationWithKeyPath:@"bounds.size"];
springAni.toValue = [NSValue valueWithCGSize:CGSizeMake(250, 100)];
springAni.mass = 5;
springAni.stiffness = 500;
springAni.damping = 15;
springAni.initialVelocity = -10;
springAni.duration = springAni.settlingDuration;
[self.redView.layer addAnimation:springAni forKey:@"spring"];
}
二、CATransition(转场动画)
主要用于从一个场景向另一个场景的动画过渡。
-
@property(copy) NSString *type;
:转场动画的l类型系统公开的有:fade
,moveIn
,push
andreveal
. Defaults to `fade'。对应系统常量:-
CA_EXTERN NSString * const kCATransitionFade
淡进淡出; -
CA_EXTERN NSString * const kCATransitionMoveIn
新视图移动到旧视图上; -
CA_EXTERN NSString * const kCATransitionPush
推出新视图 -
CA_EXTERN NSString * const kCATransitionReveal
揭开显示新视图 - 除了公开的还有一些私有的:"cube"立方体翻转效果、 "suckEffect"收缩效果、 "rippleEffect"水滴波纹效果、 "pageCurl"向上翻页效果、
"pageUnCurl"向下翻页效果、 "oglFlip"翻转效果、"cameraIrisHollowOpen"摄像头打开效果、
"cameraIrisHollowClose"摄像头关闭果。
-
-
@property(nullable, copy) NSString *subtype;
转场方向,系统体供了四个:CA_EXTERN NSString * const kCATransitionFromRight
CA_EXTERN NSString * const kCATransitionFromLeft
CA_EXTERN NSString * const kCATransitionFromTop
CA_EXTERN NSString * const kCATransitionFromBottom
@property float startProgress;
开始进度,默认为0,范围[0 , 1],如果设置0.2,那么动画将从动画的0.2的部分开始。@property float endProgress;
结束进度,默认为1,范围[0 , 1],如果设置0.7,那么动画将从动画的0.7的部分开始。务必保证endProgress大于等于startProgress。@property(nullable, strong) id filter;
自定义过滤器,基本不会用~
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
i = -1;
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 275, 400)];
self.imageView.image = [UIImage imageNamed:@"boy"];
[self.view addSubview:self.imageView];
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 550, 275, 50)];
self.nameLabel.backgroundColor = [UIColor redColor];
self.nameLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:self.nameLabel];
self.imageNames = @[@"boy", @"loli", @"romance", @"poem", @"effort"];
self.animationTypes = @[kCATransitionFade, kCATransitionMoveIn, kCATransitionPush, kCATransitionReveal, @"cube", @"suckEffect", @"rippleEffect", @"pageCurl", @"pageUnCurl", @"oglFlip", @"cameraIrisHollowOpen", @"cameraIrisHollowClose"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (i == self.animationTypes.count-1) {
i = 0;
}else{
i = i+1;
}
CATransition * transition = [CATransition animation];
transition.duration = 2.0;
transition.type = self.animationTypes[i];
transition.subtype = kCATransitionFromRight;
self.imageView.image = [UIImage imageNamed:self.imageNames[i%5]];
self.nameLabel.text = self.animationTypes[i];
[self.imageView.layer addAnimation:transition forKey:@"transition"];
}
三、CAAnimationGroup(动画组)
只有一个属性@property(nullable, copy) NSArray<CAAnimation *> *animations;
一组CAAnimation对象。 数组的每个成员将使用正常规则在父动画的时间空间中同时运行(并发)。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CABasicAnimation * baseAni = [CABasicAnimation animationWithKeyPath:@"transform"];
baseAni.duration = 5.0;
CATransform3D transform = CATransform3DMakeRotation(M_PI, 100, 100, 0);
baseAni.toValue =[NSValue valueWithCATransform3D:transform];
CASpringAnimation * springAni = [CASpringAnimation animationWithKeyPath:@"bounds.size"];
springAni.toValue = [NSValue valueWithCGSize:CGSizeMake(100, 220)];
springAni.mass = 2;
springAni.stiffness = 300;
springAni.damping = 5;
springAni.fillMode = kCAFillModeForwards;
springAni.duration = springAni.settlingDuration;
CAKeyframeAnimation * keyFrameAni = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyFrameAni.duration = 5.0;
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 100, 250, 400) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(125, 125)];
keyFrameAni.path = path.CGPath;
keyFrameAni.calculationMode = kCAAnimationPaced;
keyFrameAni.rotationMode = kCAAnimationRotateAuto;
CAAnimationGroup * groupAni = [CAAnimationGroup animation];
groupAni.animations = @[baseAni, springAni, keyFrameAni];
groupAni.duration = 5;
[self.imageView.layer addAnimation:groupAni forKey:@"group"];
}