给Layer的内容添加动画

继续继续...

Core Animation使得我们可以很轻松的为app的layer添加复杂的动画. 包括改变layer的尺寸,位置, 设置旋转, 或是改变透明度. 使用Core Animation初始化一个动画就如同改变属性一样简单, 当然你也可以自己手动创建动画并且设置各种复杂的参数.

而关于一些高级的动画, 请看Advanced Animation Tricks.

给Layer的属性添加简单的动画

根据你的需要, 你可以显示或隐式的完成简单的动画. 隐式动画使用默认的时间和动画属性来完成动画; 而显式的动画则需要你自己使用一个动画对象来配置它的动画属性. 所以当默认的时间对你够用或者是你需要很少的代码来完成改变时, 隐式的动画就非常适合.

简单的动画涉及到改变layer的属性, 然后让Core Animation根据时间参数来展现动画. Layer定义了许多属性来影响其自身的视觉表现. 比如把layer的opacity属性从1.0改到0.0, 那么layer就完全是透明的看不见了.

为了触发隐式动画, 你所需要做的就是更新layer对象的属性. 当改变了layer树上的layer对象时, 这些改变就会立即被这些layer对象反映出来. 但是layer对象的视觉表现并不会立即变化. 这里是因为Core Animation会用这些改变作为一个触发器, 以此来创建和设置一个或是更多个用于执行的隐式动画. 所以, 如下面的代码所示, 不透明度设置完以后, Core Animation会为你去创建一个动画对象, 然后设置动画的开始时间为下一次更新循环开始的时候.

theLayer.opacity = 0.0;

如果想显式的实现上面代码所实现的改变, 可以通过创建一个CABasicAnimation对象, 并且使用这个对象来配置动画参数来实现. 这里可以给动画设置起始值和结束值, 改变间隔时间, 或是改变其他动画参数, 当然这一切都需要在你将动画参数添加到layer上之前完成. 下面的代码就展示了使用动画对象实现了layer的淡出效果. 当创建动画对象时, 需要指定想要展示动画的那个属性. 为了执行动画, 则需要调用addAnimation:forKey:方法把动画添加到layer上.

CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:@"opacity"];
 
// Change the actual data value in the layer to the final value.
theLayer.opacity = 0.0;

提示: 当创建显式动画的时候, 比较推荐的做法就是总是给动画对象的fromValue属性赋值. 如果你没有给这个属性赋值的话, Core animation会使用layer当前的值作为初始值. 所以如果此时的值已经是结束值的话, 动画效果就看不到了.

隐式动画会更新layer对象的数据, 而显式动画则不会改变layer树上的数据. 显式动画只会产生动画, 动画结束时, Core Animation会把动画对象从layer上面移除, 并且用当前的数值重绘layer. 如果你想让显式动画完成真正的改变, 那你就需要像前面的例子中看到的一样, 更新layer的属性.

隐式和显式动画都是在当前运行循环结束后开始执行的, 并且当前线程必须有一个运行循环存在, 这样才能执行动画. 如果你改变了多个动画属性, 或是在一个layer上添加了多个动画对象, 所以的这些改变都会在同一时刻发生. 比如, 你可以设置一个layer在移出屏幕的同时产生淡入淡出的效果. 当然你也可以自定义动画的开始时间. 关于更多动画时间的内容, 查看Customizing the Timing of an Animation.

使用一个关键帧动画来改变Layer的属性

通过设置属性来实现动画主要是通过设置初始值和结束值实现的. 而一个CAKeyframeAnimation对象可以设置一系列线性或非线性的目标值来执行动画. 一个关键帧动画是由一组目标值和每个值所要完成的次数共同组成的. 在最简单的配置中, 你可以使用一个数组来保存设置的值以及每个值所完成的次数. 对于layer位置的改变, 你可以让改变沿着一条路径进行. 动画对象提取你指定的关键帧, 通过在给定时间内进行线性插值的方法来构建动画.

下图展示了一个长约5秒的关于layer位置变化的动画. 位置是沿着一个路径改变, 而这个路径就是通过CGPathRef数据类型来指定的. 代码如下:

给Layer的内容添加动画_第1张图片
给Layer的内容添加动画_第2张图片
// create a CGPath that implements two arcs (a bounce)
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
                                   320.0,500.0,
                                   320.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
                                   566.0,500.0,
                                   566.0,74.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.
[theLayer addAnimation:theAnimation forKey:@"position"];

指定关键帧的值

关键帧的值是关键帧动画的核心. 这些值定义了动画在执行过程中的所有行为. 设置关键帧值最主要的方法就是使用包含CGPoint数据类型的数组对象, 比如layer的anchorPointposition属性; 同时也可以使用CGPathRef类型代替.

当设置数组的值时, 存入数组的值取决于属性所需要的数据类型. 你可以直接给数组加一些对象. 但是一些对象在被添加进数组前必须先转换为id类型, 并且所有的数值类型和结构体必须装进一个对象类型里. 比如:

  • 对于接受CGRect类型的属性(比如boundsframe), 则需要把它们包装进NSValue对象中.
  • 对于layer的transform属性, 也要把CATransform3D类型包装进NSValue对象中.
  • 对于borderColor属性, 在添加进数组前要将CGColorRef转换成id类型.
  • 当对layer的contents创建动画时, 需要指定一个包含CGImageRef类型的数组.

对于接受CGPoint数据类型的属性来说, 你可以创建一个包含点的矩阵或是使用一个CGPathRef对象来指定路径. 当你使用的是点矩阵时, 关键帧动画就会在每一个连续点间绘制一条直线, 并且动画将遵循这个路径. 当使用的是CGPathRef对象时, 动画是从路径的起点开始执行, 遵循其轮廓, 包括各种曲线. 你即可以使用开路径, 也可以使用闭合路径.

指定关键帧动画的时间

关键帧动画的时间控制和节奏掌握要比基础动画更加复杂, 可以通过很多属性来控制.

  • calculationMode属性定义了用来计算动画时间的算法. 这个属性的值会影响其他和时间有关的属性将如何被使用:

    • calculationMode属性设置成kCAAnimationLinearkCAAnimationCubic时, 即线性或立体动画模式, 该模式下会使用所提供的时间信息来产生动画. 这个模式可以让你在最大程度上控制动画时间.
    • calculationMode属性设置成kCAAnimationPacedkCAAnimationCubicPaced时, 即节奏动画模式, 该模式下动画时间并不会依赖于外界值的提供, 比如keyTimestimingFunctions, 相反, 时间是通过一个常量速率计算得到.
    • calculationMode属性设置成kCAAnimationDiscrete时, 即离散动画模式, 这是从一个关键帧跳到另一个关键帧的过程中并没有插值, 即并不是看到变化的过程. 该模式下调用的是keyTimes属性, 并没并不会使用timingFunctions的值.
  • keyTimes属性设定了特定的时间标记, 即在何时使用关键帧的值. 并且只有在计算模式设置成kCAAnimationLinear, kCAAnimationDiscrete, 或kCAAnimationCubic时才会被是用到, 如果是在节奏动画中, 就不会使用的到.

  • timingFunctions指定了每个关键帧使用到的时间曲线.

如果你想自己来处理动画的时间, 那么可以使用kCAAnimationLinearkCAAnimationCubic模式, 并且设置keyTimestimingFunctions的值.

在显式动画执行过程中停止动画

动画一般情况下都是执行完才会停止, 然而你也可以使用一些技术在它没有执行完时就把动画停止了:

  • 可以调用layer的removeAnimationForKey:方法将一个简答的动画对象从layer上移除. 这个方法中使用到的key就是你在添加动画时addAnimation:forKey:方法中设置的那个key. 并且其值不能为空.
  • 也可以调用removeAllAnimations方法一次性将layer上的所有动画对象全部移除. 并且该方法也会立即将正在执行的动画对象也一并移除, 然后用现存的数据重绘layer.

注意: 隐式动画是不能直接从layer上面移除的.

当你从layer上移除一个动画对象时, Core Animation就会用当前的值重绘layer. 因为当前的值一般都是动画结束时的值, 这就会导致layer会出现一个突然的晃动. 如果你不想让layer产生这个意外的效果, 你可以使用展现树上的对象来会的结束值, 并且把它设置到layer树上的对象里.

除了停止动画, 暂时暂停动画和继续动画可以参考以下代码:

-(void)pauseLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
   layer.speed = 0.0;
   layer.timeOffset = pausedTime;
}
 
-(void)resumeLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer timeOffset];
   layer.speed = 1.0;
   layer.timeOffset = 0.0;
   layer.beginTime = 0.0;
   CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
   layer.beginTime = timeSincePause;
}

一起执行多个动画

如果你想在一个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"];

除了上面的方法, 还可以使用一个事务对象来包装动画. 事务的灵活性更高, 可以让你给组合起来的动画对象里单个对象设置动画属性. 具体可以参考Explicit Transactions Let You Change Animation Parameters.

检测动画的结束

Core Animation可以让你检测到动画的开始和结束. 而这两个时刻正好又非常适合做一些和动画有关的其他任务. 比如, 你可以在动画开始的时候设置一些相关的状态信息, 在动画结束的时候取消之前设置的信息.

有两种方式可以获得动画状态信息的通知:

  • 使用setCompletionBlock:方法给事务在完成的时候加一个block, 当事务里所有的动画都结束了的时候, 就会调用这个block.
  • CAAnimation对象指定一个代理, 然后实现两个代理方法: animationDidStart:animationDidStop:finished:.

如果想使两段动画连续进行, 即一个结束后, 另一个开始, 那就不要使用动画通知了. 这里需要使用动画对象的beginTime属性来设置相应的开始时间, 将第二个动画的开始时间设置成第一个动画的结束时间. 更多详细内容可以看Customizing the Timing of an Animation.

如何为基于layer的view添加动画

如果一个layer是基于一个view的, 那么添加动画比较推荐的方法就是使用UIKit或AppKit提供的动画接口. 使用Core Animation的接口可以直接给layer添加动画, 但是要注意的就是iOS 和 OS X平台是有区别的, 这里只赘述iOS平台的方式.

iOS中如何修改layer

因为iOS中的view总是自带layer的, 所以UIView类自己可以直接从layer对象得到数据. 所以, 对layer的改变就会直接反映在view上. 这也就意味着你即可以使用Core Animation, 也可以使用UIView提供的接口来完成改变.

如果是使用Core Animation来初始化动画, 所有关于Core Animation的调用都要在一个基于view的动画block中进行. UIView类默认是不能给layer添加动画的, 只有在block中才可以. 所以如果关于动画的代码都写在了block之外, 那么是没有动画产生的. 下面的代码展示了如何改变layer的opacityposition属性.

[UIView animateWithDuration:1.0 animations:^{
   // Change the opacity implicitly.
   myView.layer.opacity = 0.0;
 
   // Change the position explicitly.
   CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
   theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
   theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
   theAnim.duration = 3.0;
   [myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];

你可能感兴趣的:(给Layer的内容添加动画)