1. 隐式动画:修改layer的某属性,默认生成一个动画,动画的时间等属性是默认的,如时间为0.25s,可以用CATransaction进行修改。
theLayer.opacity = 0.0;
2. 显式动画:手动生成CABasicAnimation等animation类,设置所需的动画属性,然后将其添加到layer上
- 生成animation:
animationWithKeyPath:
,keyPath为指定动画类型,如 position; - 添加animation:
addAnimation: forKey:
,key为动画的key,可用于获取指定动画对象; - 获取animation:
animationForKey:
,key为addAnimation: forKey:
中的key; - 删除animation:
removeAnimationForKey:
或removeAllAnimations
,可用于停止指定动画,但此种停止方式会产生一个当前状态跳到最终状态或所设置状态的闪动;隐式动画直接停止不了,可用CATransaction停止。 - 暂停动画:采用layer的speed、timeOffset、beginTime属性
2.1 CABasicAnimation动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 100)];
animation.toValue = [NSValue valueWithCGPoint:endPoint];
animation.duration = 3;
[animationLayer addAnimation:animation forKey:@"animaton-position"];
fromValue如果没有没有指定,则默认为现在的状态值。
图层动画的本质就是将图层内部的内容转化为位图经硬件操作形成一种动画效果,其实图层本身并没有任何的变化。上面的动画中图层并没有因为动画效果而改变它的位置(对于缩放动画其大小也是不会改变的),所以动画完成之后图层还是在原来的显示位置没有任何变化,如果这个图层在一个UIView中你会发现在UIView移动过程中你要触发UIView的点击事件也只能点击原来的位置(即使它已经运动到了别的位置),因为它的位置从来没有变过。当然解决这个问题方法比较多,这里不妨在动画完成之后重新设置它的位置。
//animationLayer.position = [[anim valueForKey:@"AnimationLocation"] CGPointValue];//直接设置最终位置,会产生一个从初始位置到最终设置位置的快速隐式动画,解决这个问题有两种办法:关闭图层隐式动画、设置动画图层为根图层
但会出现另外一个问题,那就是动画运行完成后会产生重新从起始点运动到终点的动画。这个问题产生的原因就是前面提到的,对于非根图层,设置图层的可动画属性(在动画结束后重新设置了position,而position是可动画属性)会产生动画效果。解决这个问题可以关闭图层隐式动画:
animation.delegate = self;//得放在addAnimation:之前,否则代理不会调用
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
NSArray *subLayers = [self.view.layer sublayers];
CALayer *animationLayer = [subLayers lastObject];
//animationLayer.position = [[anim valueForKey:@"AnimationLocation"] CGPointValue];//直接设置最终位置,会产生一个从初始位置到最终设置位置的快速隐式动画,解决这个问题有两种办法:关闭图层隐式动画、设置动画图层为根图层
//关闭图层隐式动画
//开启事务
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];
animationLayer.position=[[anim valueForKey:@"AnimationLocation"] CGPointValue];//设置最终位置,否则,layer会回到初始位置,动画并没有改变layer。
//提交事务
[CATransaction commit];
}
这种方式可能在动画运行完之后出现从原点瞬间回到终点的闪动,可以在动画前设置最终状态位置:
animationLayer.position = CGPointMake(350, 100);//加在添加动画前,先把layer的状态设为动画的最后状态,可避免动画完成后产生一个从初始位置到最终设置位置的快速隐式动画;若加在添加动画后,会产生一个从初始位置到最终设置位置的快速隐式动画,然后再开始添加的动画;采用代理的方式,在动画结束后,设置layer的最终状态(删除隐式动画),则可能出现一个从初始位置到最终设置位置的快速闪动。
2.2 CAKeyframeAnimation动画
关键帧动画开发分为两种形式:一种是通过设置不同的属性值进行关键帧控制,另一种是通过绘制路径进行关键帧控制。后者优先级高于前者,如果设置了路径则属性值就不再起作用。
- 通过属性值产生动画:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.values = [NSArray arrayWithObjects:[NSValue valueWithCGPoint:CGPointMake(50, 100)], [NSValue valueWithCGPoint:CGPointMake(150, 200)], [NSValue valueWithCGPoint:CGPointMake(50, 300)], [NSValue valueWithCGPoint:endPoint], nil];
animation.duration = 4;
animation.calculationMode = kCAAnimationPaced;
animation.keyTimes = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.3], [NSNumber numberWithFloat:0.6], [NSNumber numberWithFloat:0.7], [NSNumber numberWithFloat:1.0], nil];
animation.delegate = self;//得放在addAnimation:之前,否则代理不会调用
//存储当前位置在动画结束后使用
[animation setValue:[NSValue valueWithCGPoint:endPoint] forKey:@"AnimationLocation"];
[animationLayer addAnimation:animation forKey:@"animation-point"];
values属性:赋值给他的各属性值先得转化为对象,针对不同类型的值,可能需转化为不同的对象类型
- CGPoint转化为NSValue类型;
- CGRect类型则转化为NSValue类型;
- transform属性,CATransform3D类型转化为NSValue类型;
- borderColor属性,CGColorRef类型转换为id对象类型;
- CGFloat类型转化为NSNumber类型;
- contents属性,赋值CGImageRef类型。
calculationMode属性:
- kCAAnimationLinear:每个keyValue间匀速;
- kCAAnimationCubic:平滑移动;
- kCAAnimationDiscrete:跳动,没有中间的滑动;
- kCAAnimationPaced:整个动画匀速,动画时间相关参数默认设置好,ketTimes属性不起作用;
- kCAAnimationCubicPaced:整个动画平滑匀速,动画时间相关参数默认设置好,ketTimes属性不起作用。
keyTimes属性:表示各段的动画时间,当calculationMode为kCAAnimationDiscrete时,需要多添一个time值,最后一个时间间隔用于动画结束后返回layer的初始状态或新设置的,kCAAnimationPaced和kCAAnimationCubicPaced对time属性不起作用,其他的keyValue数与time数相同,代表value的起始时间;对于keyValues类型,标识各个value间的起始时间,个数为value数相同(kCAAnimationDiscrete会多一个);对于path则表示各个线段的起始时间,包括直线或曲线。
- 通过路径path属性产生动画
animationLayer.position = CGPointMake(350, 100);//加在添加动画前,先把layer的状态设为动画的最后状态,可避免动画完成后产生一个从初始位置到最终设置位置的快速隐式动画;若加在添加动画后,会产生一个从初始位置到最终设置位置的快速隐式动画,然后再开始添加的动画;采用代理的方式,在动画结束后,设置layer的最终状态(删除隐式动画),则可能出现一个从初始位置到最终设置位置的快速闪动。
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathMoveToPoint(pathRef, NULL, 50, 100);
CGPathAddCurveToPoint(pathRef, NULL, 70, 200, 230, 200, 250, 100);
CGPathAddCurveToPoint(pathRef, NULL, 270, 200, 330, 200, 350, 100);
animation.path = pathRef;
CGPathRelease(pathRef);
animation.duration = 3;
//animation.beginTime = CACurrentMediaTime() + 2;//动画延迟2秒执行,此beginTime为图层的本地时间
animation.calculationMode = kCAAnimationLinear;
animation.keyTimes = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.3], [NSNumber numberWithFloat:1.0], nil];
animation.delegate = self;//得放在addAnimation:之前,否则代理不会调用
//存储当前位置在动画结束后使用
[animation setValue:[NSValue valueWithCGPoint:CGPointMake(350, 100)] forKey:@"AnimationLocation"];
[animationLayer addAnimation:animation forKey:@"animation-point"];
2.3 CAAnimationGroup动画组
// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;
// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor, nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced;
// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;
[myLayer addAnimation:group forKey:@"BorderChanges"];
2.4 动画结束操作
1. 设置动画完成的block,setCompletionBlock:
2. 设置代理实现animationDidStart:
andanimationDidStop:finished:
方法
//pragma mark 动画开始
-(void)animationDidStart:(CAAnimation *)anim
{
}
//pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
}
2.5 动画暂停与恢复
CALayer实现了CAMediaTiming协议. CALayer通过CAMediaTiming协议实现了一个有层级关系的时间系统.除了CALayer,CAAnimation也采纳了此协议,用来实现动画的时间系统.
在CA中,有一个Absolute Time(绝对时间)的概念,可以通过CACurrentMediaTime()获得,其实这个绝对时间就是将mach_absolute_time()转换成秒后的值.这个时间和系统的uptime有关,系统重启后CACurrentMediaTime()会被重置.
就和座标存在相对座标一样,不同的实现了CAMediaTiming协议的存在层级关系的对象也存在相对时间,经常需要进行时间的转换,CALayer提供了两个时间转换的方法:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
现在来重点研究CAMediaTiming协议中几个重要的属性
- beginTime
无论是图层还是动画,都有一个时间线Timeline的概念,他们的beginTime是相对于父级对象的开始时间. 虽然苹果的文档中没有指明,但是通过代码测试可以发现,默认情况下所有的CALayer图层的时间线都是一致的,他们的beginTime都是0,绝对时间转换到当前Layer中的时间大小就是绝对时间的大小.所以对于图层而言,虽然创建有先后,但是他们的时间线都是一致的(只要不主动去修改某个图层的beginTime),所以我们可以想象成所有的图层默认都是从系统重启后开始了他们的时间线的计时.
但是动画的时间线的情况就不同了,当一个动画创建好,被加入到某个Layer的时候,会先被拷贝一份出来用于加入当前的图层,在CA事务被提交的时候,如果图层中的动画的beginTime为0,则beginTime会被设定为当前图层的当前时间,使得动画立即开始.如果你想某个直接加入图层的动画稍后执行,可以通过手动设置这个动画的beginTime,但需要注意的是这个beginTime需要为 CACurrentMediaTime()+延迟的秒数,因为beginTime是指其父级对象的时间线上的某个时间,这个时候动画的父级对象为加入的这个图层,图层当前的时间其实为[layer convertTime:CACurrentMediaTime() fromLayer:nil],其实就等于CACurrentMediaTime(),那么再在这个layer的时间线上往后延迟一定的秒数便得到上面的那个结果. - timeOffset
这个timeOffset可能是这几个属性中比较难理解的一个,官方的文档也没有讲的很清楚. local time也分成两种一种是active local time 一种是basic local time.
timeOffset则是active local time的偏移量.
你将一个动画看作一个环,timeOffset改变的其实是动画在环内的起点,比如一个duration为5秒的动画,将timeOffset设置为2(或者7,模5为2),那么动画的运行则是从原来的2秒开始到5秒,接着再0秒到2秒,完成一次动画. - speed
speed属性用于设置当前对象的时间流相对于父级对象时间流的流逝速度,比如一个动画beginTime是0,但是speed是2,那么这个动画的1秒处相当于父级对象时间流中的2秒处. speed越大则说明时间流逝速度越快,那动画也就越快.比如一个speed为2的layer其所有的父辈的speed都是1,它有一个subLayer,speed也为2,那么一个8秒的动画在这个运行于这个subLayer只需2秒(8 / (2 * 2)).所以speed有叠加的效果. - fillMode
fillMode的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后。如果是一个动画CAAnimation,则需要将其removedOnCompletion设置为NO,要不然fillMode不起作用. 下面来讲各个fillMode的意义
- kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
- kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
- kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
- kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态.
beginTime为本图层相对于父图层的时间,为相对时间;动画时间为当前图层时间 + beginTime,即为当前绝对时间;如在此基础上加1,则动画会往前倒1秒开始显示(此时本图层的开始时间已经超出父图层时间,即绝对时间,相当于动画暂停处的前面一秒),减一的话就是往后1秒开始显示
timeOffset在当前动画timeOffset后的动画开始显示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];
NSArray *subLayers = [self.view.layer sublayers];
CALayer *animationLayer = [subLayers lastObject];
CAAnimation *animation = [animationLayer animationForKey:@"animation-point"];
CFTimeInterval timeInterval = [animationLayer convertTime:CACurrentMediaTime() fromLayer:nil];
NSLog(@"CurrentMediaTime:%f",timeInterval);
if (animation) {
if (0 == animationLayer.speed) {
[self resumeAnimation];
} else {
[self pauseAnimation];
}
} else {
NSInteger mode = 3;
[self addAnimationToLayer:mode withPoint:point];
animationLayer.beginTime = 1;
}
}
- (void)pauseAnimation
{
NSArray *subLayers = [self.view.layer sublayers];
CALayer *animationLayer = [subLayers lastObject];
CFTimeInterval timeInterval = [animationLayer convertTime:CACurrentMediaTime() fromLayer:nil];
animationLayer.timeOffset = timeInterval;
animationLayer.speed = 0;
}
- (void)resumeAnimation
{
NSArray *subLayers = [self.view.layer sublayers];
CALayer *animationLayer = [subLayers lastObject];
animationLayer.beginTime = CACurrentMediaTime() - animationLayer.timeOffset;//beginTime为本图层相对于父图层的时间,为相对时间;动画时间为当前图层时间 + beginTime,即为当前绝对时间;如在此基础上加1,则动画会往前倒1秒开始显示(此时本图层的开始时间已经超出父图层时间,即绝对时间,相当于动画暂停处的前面一秒),减一的话就是往后1秒开始显示
animationLayer.timeOffset = 0;//在当前动画timeOffset后的动画开始显示
animationLayer.speed = 1;
}
3. CATransaction 事务
CATransaction类管理着animation的生成、执行等,大多数情况下Core Animation自动为layer生成,也可以自己手动为显式动画生成。开始一个CATransaction,调用begin方法;结束的话,调用commit方法。
CATransaction可用于修改animation的参数,如duration、timeFunction。通过调用setValue:forKey:
方法,为特定的key赋值
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];//将default duration改为10秒
// Perform the animations
[CATransaction commit];
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];//禁用隐式动画
animationLayer.position=[[anim valueForKey:@"AnimationLocation"] CGPointValue];//设置最终位置,否则,layer会回到初始位置,动画并没有改变layer。
//提交事务
[CATransaction commit];
4. CATransform3D
http://blog.youkuaiyun.com/yongyinmg/article/details/38780953
layer 的3D并非真实的3D,是3D效果图像在xy平面上的投影。视点为m34属性确定
transform.m34 = -1/z,视点则在z轴上,离原点距离z