CoreAnimation-Layer动画
CoreAnimation提供的基础框架可以轻松应用层的复杂复杂动画,并可以扩展到拥有layer的view中。下面的实例中有改变frame的大小,改变layer的位置,旋转,透明度的动画。使用CoreAnimation,启动这些动画只需要更改属性即可,你也可以现实的创建动画并设置动画参数。
改变layer属性的动画
我们可以根据需要隐式或者显示的显示动画。隐式动画使用默认的计时和动画属性来执行动画,而显式动画则需要我们使用动画对象自己配置这些属性。因此,隐式动画适合场景是我们想要在没有大量代码的情况下,并且默认时间可以满足我们的要求。
简单的动画我们只需要更改layer的属性就可以了。让coreAnimation随时间动画显示这些更改就可以了。layer中定义了许多可以影响layer外观的属性。更改其中的一个属性就是一种动画。例如:将layer的 opacity从1.0 更改到0.0 ,这样会让layer淡出最终变成透明。
重要提示:尽管我们有时候可以使用CoreAnimation 接口直接为view设置动画,但这样做通常需要额外的步骤。如何将coreAnimation于view结合使用在下面。
触发隐式动画,我们需要做的就是更新layer对象的属性。在layer trre中修改layer 对象的时候,这些对象会立即出现我们所要的改变。但是,layer对象的视觉外观不会立即更改。相反,CoreAnimation 用这些改变作为触发器来创建和安排一个多多个隐式动画以供执行。因此,进行下列更改会导致coreAnimation为我们创建一个动画对象,并让动画在下一个周期开始运行。
隐式动画
theLayer.opacity = 0.0;
显式动画
如果用显示动画如何做呢?我们需要创建 CABasicAnimation对象用这个对象配置动画参数。
MYLayer * layer = [MYLayer layer];
layer.backgroundColor = [UIColor redColor].CGColor;
layer.bounds = self.bounds;
[self.layer addSublayer:layer];
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[layer addAnimation:fadeAnim forKey:@"opacity"];
layer.opacity = 0.0;
显式动画
小提示:当我们创建显式动画,推荐总是指定fromValue的值,否则,CoreAnimation使用layer的currentValue。如果我们已经更新到了属性的最终结果,我们可能就得不到最终想要的结果。
显式动画与隐式动画不同之处在于:显式动画不会更改layer tree中的数据,而隐式动画会修改layertree中的值。显式动画仅仅是生成动画。在动画结束的时候,CoreAnimation从图层中移除动画对象,并使用当前数据值重新绘制layer。如果我们希望永久更新layer的属性,那么必须更新layer的属性,入上面的例子。
其实隐式动画更改的layer tree,显示动画更改的presention tree
隐式或者显示动画通常是在runloop结束后的下个周期开始执行,并且当前线程必须有runloop才能执行动画。如果更改多个属性,那么就会生成多个动画增加到layer中,这些属性改变是同时发生的。例如:我们可以通过同时配置两个动画来淡化layer,同时将其移除屏幕。我们也可以将动画对象配置成特定时间启动,或者执行次数。
用keyframe 动画改变layer 属性
基础属性的动画是从起始值更改到结束值,但是CAKeyframeAnimation对象允许我们可能不是线型的方式设置一组目标值的动画。关键帧动画是由一组目标值和达到每个目标值所用的时间构成。在最简单的配置中,我们用数组指定目标值和时间。
更改layer的位置,我们可以用一个path路径来更改。动画对象会获取指定的关键帧并在给定时间段内从一个值插值到下一个值来构建动画
MYLayer * layer = [MYLayer layer];
layer.bounds = CGRectMake(0, 0, 30, 30);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.layer addSublayer:layer];
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,0,0);
CGPathAddCurveToPoint(thePath,NULL,30,300,
100,300,
100,0);
CGPathAddCurveToPoint(thePath,NULL,100,300,
230,300,
230,0);
CAKeyframeAnimation * theAnimation;
// Create the animation object, specifying the position property as the key path.
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
theAnimation.path=thePath;
theAnimation.duration=5.0;
// Add the animation to the layer.
[layer addAnimation:theAnimation forKey:@"position"];
layer.position =CGPointMake(230, 0);
keyPathFrame
指定关键帧的值
keyframe动画的最关键部分就是keyFrame的值了,这些值定义了动画在执行过程中的行为。指定关键帧动画的主要方法就是设置指定这些值。不过也可以用CGPathRef 数据类型代替。
当我们指定值到数组中,放入数组的内容取决于属性所需要的类型。我们可以直接向数组中添加一些对象;但是,有些对象必须强制转换成id类型才行。并且所有标量类型或者结构必须由对象包装。
- 比如属性需要CGRect类型变量,我们需要将请包装成NSValue对象
- 对于layer的transform属性或者CATransfrm3d 属性也需要包装成NSValue
- 对于borderColor属性,需要用CGColorRef数据
- 对于float类型的属性,我们需要转换成NSNumber
- 如果我们需要更改contents的内容时候,我们需要再数组中添加CGImageRef;
这里对于CGPoint类型的属性,我们可以用NSValue·包装成数组,也可以用CGPathRef对象。当有数组装有CGPoint,那么keyFrame是以两点之间是以直线的形式做动画,而用CGPathRef,可以是曲线的形式做动画。用CGPathRef路径做动画,path路径可以是闭合的也可以是打开的。
用values属性更改border的颜色值
MYLayer * layer = [MYLayer layer];
layer.borderWidth = 10;
layer.position= CGPointMake(150, 150);
layer.bounds = CGRectMake(0, 0, 130, 130);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.layer addSublayer:layer];
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,0,0);
CGPathAddCurveToPoint(thePath,NULL,30,300,
100,300,
100,0);
CGPathAddCurveToPoint(thePath,NULL,100,300,
230,300,
230,0);
CAKeyframeAnimation * theAnimation;
// Create the animation object, specifying the position property as the key path.
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray * colorArr=@[(id)(UIColor.redColor.CGColor),(id)(UIColor.greenColor.CGColor),(id)(UIColor.purpleColor.CGColor)];
theAnimation.values = [colorArr copy];
theAnimation.duration=5.0;
// Add the animation to the layer.
[layer addAnimation:theAnimation forKey:@"borderColor"];
animation.gif
指定keyframe的动画的时间
关键帧动画的时间和步调比基本动画更复杂。我们可以通过以下几个属性来控制他。
- calculationMode 属性定义用于计算动画计时的算法。该属性会影响其他的与时序有关系的属性的使用。
线型(kCAAnimationLinear)或者立体(kCAAnimationCubic)动画使用提供的时序信息生成动画。这些模式可以让我们最大程度地控制动画时序。
paced动画( kCAAnimationPaced和kCAAnimationCubicPaced)不依赖keyTimes或者timingFunctions属性提供的外部定时值。相反的,隐式计算定时值以提供恒定速度的动画。
离散动画( kCAAnimationDiscrete),使动画属性从一个关键帧动画跳转到下一个关键帧动画不进行插值操作。次计算使用keytimes属性中的值,但是忽略timingFunctions属性。
)
- keytimes 属性指定应用每个关键帧值的时间标记。只有模式是kCAAnimationLinear,kCAAnimationDiscrete或kCAAnimationCubic时才使用此属性。
- timingFunctions 属性指定用于每个关键帧段的时序曲线。
如果我们想自定义动画,用kCAAnimationLinear 或者kCAAnimationCubic mode,和keyTimes以及timingFunctions 函数
keyTimes 定义申请每个关键帧动画的指定的时间点,所有的这些键入的时间点都是被timing functions函数控制的。
timing functions函数能控制快进快出等。要是不设置,那么默认使用线型的时间关系。
MYLayer * layer = [MYLayer layer];
layer.position= CGPointMake(15, 15);
layer.bounds = CGRectMake(0, 0, 30, 30);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.layer addSublayer:layer];
CAKeyframeAnimation * theAnimation;
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
// theAnimation.calculationMode
// theAnimation.calculationMode = kCAAnimationLinear;
NSMutableArray * array = [NSMutableArray array];
NSValue * value =[ NSValue valueWithCGPoint:CGPointMake(15, 15)];
[array addObject:value];
value =[ NSValue valueWithCGPoint:CGPointMake(15, 280)];
[array addObject:value];
value =[ NSValue valueWithCGPoint:CGPointMake(280, 280)];
[array addObject:value];
value =[ NSValue valueWithCGPoint:CGPointMake(280, 15)];
[array addObject:value];
value =[ NSValue valueWithCGPoint:CGPointMake(15, 15)];
[array addObject:value];
theAnimation.values = [array copy];
theAnimation.keyTimes =@[@0,@0.1,@0.3,@0.5,@1.0];
NSArray * timeFunctionArray =@[kCAMediaTimingFunctionDefault,kCAMediaTimingFunctionEaseIn,kCAMediaTimingFunctionEaseInEaseOut,kCAMediaTimingFunctionEaseOut];
NSMutableArray * timeArr = [NSMutableArray array];
for (NSString * timesStr in timeFunctionArray) {
[timeArr addObject:[CAMediaTimingFunction functionWithName:timesStr]];
}
theAnimation.timingFunctions=[timeArr copy] ;
theAnimation.duration=10;
// Add the animation to the layer.
[layer addAnimation:theAnimation forKey:@"position"];
停止正在运行中的显式动画
动画正常情况下是按部就班的执行完毕动画,但有时候我们需要提前停止动画。那么我们就需要用到下列技术了
- 从layer中删除动画对象。调用layer的removeAnimationForKey:方法删除正在运行的动画。这个方法仅能通过 addAnimation:forKey:
方法添加的动画。 - 移除所有动画。调用 removeAllAnimations方法
注意,我们是没有办法从layer中直接移除隐式动画
当我们删除动画的时候,core Animation会用当前值重新绘制layer。因为当前值一般是动画结束时候的状态值。这样可能导致闪屏。如果我们想让动画停留在正在执行的动画的当前状态,那么我们需要检索presentation tree,将检索值重新设置到layer值中。
多动画一起执行
如果我们想执行多个动画,那么我们需要用 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"];
CAAnimationGroup
检测动画结束
coreAnimation 支持检测动画的开始和结束。这些通知是处理动画事务的很好的时机。例如:我们可以用开始通知设置一些状态信息,使用结束通知来拆除该状态
有两种方式通知动画的状态:
+给transaction用setCompletionBlock:
增加一个block.当所有动画结束时候,transation执行这个block
- 给CAAnimation对象设置一个代理,通过animationDidStart:和animationdidStop:finished:方法获取开始还是结束
如果我们想在一个动画结束执行另一个动画,不要用动画通知。用动画的beginTime属性在所需要的时间开启。要将两个动画连接在一起,应该将第二个动画的开始时间设置为第一个动画的结束时间。
怎么使用view动画
如果view需要动画,最好的方式是使用UIKIt提供的动画接口。
修改layer规则
在ios中,view始终有个layer,因此,UIView类本事直接从layer对象中派生出其大部分数据。因此,我们对view所做的改变也会自动的反应到layer中。这意味着我们可以通过CoreAnimation或者UIView接口对其进行修改。
如果我们使用CoreAnimation类初始化动画,则必须基于view的动画block中发出所有的CoreAnimation 调用。UIview类默认禁止图层动画,但在block中可以重新启用他们。因此,我们在block之外所有的任何改变都不会生成动画。下列例子展示如何隐式更改图层的不透明度以及显示位置。
[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
self.layer.opacity = 0.0;
// Change the position explicitly.
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:self.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100 )];
theAnim.duration = 3.0;
[self.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];
在该实例中,myNewPosition变量预先计算并被block捕获到。两个动画同事开始,但是不透明动画以默认时序运行,而位置动画以指定的时间运行。
改变layer的默认行为
CoreAnimation 使用action对象实现的layer的隐式动画行为。一个action对象实现了CAAction协议,他定义了在layer执行的相关行为。所以CAAnimation对象都实现了该协议,并且通常会在layer属性更改时候分配这些对象。
动画属性其实就是一种定义的action对象行为。action对象必须与layer相关联才有用。
自定义Action Object
要创建自定义的action对象,必须实现CAAction协议,并实现runActionForKey:object:arguments:方法。在改方法中,使用有效信息执行我们需要再layer执行的操作。我们可以使用该方法将动画对象添加到layer上。也可以使用他来执行其他的任务。
定义action 对象时候,必须确定要知道如何触发该操作。对于action的触发器是稍后注册该action的key。我们可以通过下面方式触发action 对象。
- layer属性的值发生改变的时候。可以是layer的任何属性,不仅仅是动画属性。(我们可以将action绑定到自定义的属性上)。key是属性的名字。
- layer变成可视或者增加到layer的层次树中。标示此action的key是kCAOnOrderIn
- layer从layer的层次树中删除。标示此action的key是kCAOnOrderOut
- layer将参与过渡动画。标示此action的key是kCATransition
action必须要安装在layer上才能生效
一个action对象被执行以前,该layer需要找到要执行相应的action 对象。与action对象关联的key就是要修改的属性的name或者一个专有标示此操作的字符串。当layer上发生事件时候,layer会调用actionForKey:方法来搜索与该关键字相关联的操作对象。在此搜索过程中,我们可以在以下几个位置插入自己,并为该key提供相关的操作对象。
CoreAnimation是通过下列属性查看action 对象的
- 1.如果layer有delegate对象,并且delegate对象实actionForLayer:forKey:方法。layer将调用该方法。delegate必须做下列行为之一
返回该key的action对象
如果不处理该action,返回nil,在该种情况下,coreAnimation还将继续搜索。
返回一个NSNull 对象,coreAnimation 将结束搜索。
- 2.查看layer的action字典,是否有该key对应的action
- 3.layer 从style 字典中继续查找是否包含该key的action
- 4.layer调用方法 defaultActionForKey:方法
- 5.如果找到了就执行coreAnimation定义的隐式动画。
如果我们在任何比较适合的搜索点提供了action对象。layer将停止搜索并执行该操作对象。当coreAnimation找到了action对象,layer将调用action对象的runActionForKey:object:arguments:方法来执行操作。
安装action对象位置取决于我们打算如何修改layer
- 对于仅是在特定情况下使用的action,或者已经使用delegate的layer,我们就在delegate中实现actionForLayer:forKey:方法 就可以了
- 如果layer没有使用delegate,我们将action添加到layer的actions字典中就行了
- 对于关联layer普通属性的action,我们在style字典中包含该属性就可以了。
- 对于layer很重要的操作,我们生成一个子类实现defaultActionForKey:方法
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor redColor];
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor blueColor];
button.frame =CGRectMake(0, 0, 100, 50);
[button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchDown];
[self addSubview:button];
}
return self;
}
-(void)button:(UIButton *)button{
}
- (id<CAAction>)actionForLayer:(CALayer *)theLayer
forKey:(NSString *)theKey {
CATransition *theAnimation=nil;
NSLog(@"%@",theKey);
if ([theKey isEqualToString:@"position"]) {
theAnimation = [[CATransition alloc] init];
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}
return theAnimation;
}
delegate方式实现的
我们知道view中包含一个layer ,而这个layer的delegate对象其实就是view。因此我们可以在view中实现- (id<CAAction>)actionForLayer:(CALayer *)theLayer
forKey:(NSString *)theKey方法,替换其中方法。
使用CATransaction类禁用操作
当我们不想让其执行动画的时候我们可以用下列代码禁用动画
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];