[iOS Animation]-CALayer 显示动画 关键帧动画

关键帧动画

CABasicAnimation揭示了大多数隐式动画背后依赖的机制,这的确颇有趣,可是显式地给图层添加CABasicAnimation相较于隐式动画而言,只能说费力不讨好。 git

 CAKeyframeAnimation 是另外一种UIKit没有暴露出来但功能强大的类。和 CABasicAnimation 相似, CAKeyframeAnimation 一样是 CAPropertyAnimation 的一个子类,它依然做用于单一的一个属性,可是和 CABasicAnimation 不同的是,它不限制于设置一个起始和结束的值,而是能够根据一连串随意的值来作动画。 github

关键帧起源于传动动画,意思是指主导的动画在显著改变发生时重绘当前帧(也就是关键帧),每帧之间剩下的绘制(能够经过关键帧推算出)将由熟练的艺术家来完成。 CAKeyframeAnimation 也是一样的道理:你提供了显著的帧,而后Core Animation在每帧之间进行插入。 数组

咱们能够用以前使用颜色图层的例子来演示,设置一个颜色的数组,而后经过关键帧动画播放出来(清单8.5) app

清单8.5 使用 CAKeyframeAnimation 应用一系列颜色的变化 函数

复制代码
- (IBAction)changeColor { //create a keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"backgroundColor"; animation.duration = 2.0; animation.values = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor greenColor].CGColor, (__bridge id)[UIColor blueColor].CGColor ]; //apply animation to layer  [self.colorLayer addAnimation:animation forKey:nil]; }
复制代码

 

注意到序列中开始和结束的颜色都是蓝色,这是由于CAKeyframeAnimation并不能自动把当前值做为第一帧(就像CABasicAnimation那样把fromValue设为nil)。动画会在开始的时候忽然跳转到第一帧的值,而后在动画结束的时候忽然恢复到原始的值。因此为了动画的平滑特性,咱们须要开始和结束的关键帧来匹配当前属性的值。 动画

固然能够建立一个结束和开始值不一样的动画,那样的话就须要在动画启动以前手动更新属性和最后一帧的值保持一致,就和以前讨论的同样。 atom

咱们用 duration 属性把动画时间从默认的0.25秒增长到2秒,以便于动画作的不那么快。运行它,你会发现动画经过颜色不断循环,但效果看起来有些奇怪。缘由在于动画以一个恒定的步调在运行。当在每一个动画之间过渡的时候并无减速,这就产生了一个略微奇怪的效果,为了让动画看起来更天然,咱们须要调整一下缓冲,第十章将会详细说明。 spa

提供一个数组的值就能够按照颜色变化作动画,但通常来讲用数组来描述动画运动并不直观。CAKeyframeAnimation有另外一种方式去指定动画,就是使用CGPath。path属性能够用一种直观的方式,使用Core Graphics函数定义运动序列来绘制动画。 设计

咱们来用一个宇宙飞船沿着一个简单曲线的实例演示一下。为了建立路径,咱们须要使用一个三次贝塞尔曲线,它是一种使用开始点,结束点和另外两个控制点来定义形状的曲线,能够经过使用一个基于C的Core Graphics绘图指令来建立,不过用UIKit提供的UIBezierPath类会更简单。 code

咱们此次用CAShapeLayer来在屏幕上绘制曲线,尽管对动画来讲并非必须的,但这会让咱们的动画更加形象。绘制完CGPath以后,咱们用它来建立一个CAKeyframeAnimation,而后用它来应用到咱们的宇宙飞船。代码见清单8.6,结果见图8.1。

清单8.6 沿着一个贝塞尔曲线对图层作动画

复制代码
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //create a path UIBezierPath *bezierPath = [[UIBezierPath alloc] init]; [bezierPath moveToPoint:CGPointMake(0, 150)]; [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)]; //draw the path using a CAShapeLayer CAShapeLayer *pathLayer = [CAShapeLayer layer]; pathLayer.path = bezierPath.CGPath; pathLayer.fillColor = [UIColor clearColor].CGColor; pathLayer.strokeColor = [UIColor redColor].CGColor; pathLayer.lineWidth = 3.0f; [self.containerView.layer addSublayer:pathLayer]; //add the ship CALayer *shipLayer = [CALayer layer]; shipLayer.frame = CGRectMake(0, 0, 64, 64); shipLayer.position = CGPointMake(0, 150); shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:shipLayer]; //create the keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 4.0; animation.path = bezierPath.CGPath; [shipLayer addAnimation:animation forKey:nil]; } @end
复制代码

 

图8.1

图8.1 沿着一个贝塞尔曲线移动的宇宙飞船图片

运行示例,你会发现飞船的动画有些不太真实,这是由于当它运动的时候永远指向右边,而不是指向曲线切线的方向。你能够调整它的affineTransform来对运动方向作动画,但极可能和其它的动画冲突。

幸运的是,苹果预见到了这点,而且给CAKeyFrameAnimation添加了一个rotationMode的属性。设置它为常量kCAAnimationRotateAuto(清单8.7),图层将会根据曲线的切线自动旋转(图8.2)。

清单8.7 经过rotationMode自动对齐图层到曲线

复制代码
- (void)viewDidLoad { [super viewDidLoad]; //create a path  ... //create the keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 4.0; animation.path = bezierPath.CGPath; animation.rotationMode = kCAAnimationRotateAuto; [shipLayer addAnimation:animation forKey:nil]; }
复制代码

 

图8.2

图8.2 匹配曲线切线方向的飞船图层

虚拟属性

以前提到过属性动画其实是针对于关键路径而不是一个键,这就意味着能够对子属性甚至是虚拟属性作动画。可是虚拟属性究竟是什么呢?

考虑一个旋转的动画:若是想要对一个物体作旋转的动画,那就须要做用于transform属性,由于CALayer没有显式提供角度或者方向之类的属性,代码如清单8.8所示

清单8.8 用transform属性对图层作动画

复制代码
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add the ship CALayer *shipLayer = [CALayer layer]; shipLayer.frame = CGRectMake(0, 0, 128, 128); shipLayer.position = CGPointMake(150, 150); shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:shipLayer]; //animate the ship rotation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform"; animation.duration = 2.0; animation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeRotation(M_PI, 0, 0, 1)]; [shipLayer addAnimation:animation forKey:nil]; } @end
复制代码

 

这么作是可行的,但看起来更由于是运气而不是设计的缘由,若是咱们把旋转的值从M_PI(180度)调整到2 * M_PI(360度),而后运行程序,会发现这时候飞船彻底不动了。这是由于这里的矩阵作了一次360度的旋转,和作了0度是同样的,因此最后的值根本没变。

如今继续使用M_PI,但此次用byValue而不是toValue。也许你会认为这和设置toValue结果同样,由于0 + 90度 == 90度,但实际上飞船的图片变大了,并无作任何旋转,这是由于变换矩阵不能像角度值那样叠加。

那么若是须要独立于角度以外单独对平移或者缩放作动画呢?因为都须要咱们来修改transform属性,实时地从新计算每一个时间点的每一个变换效果,而后根据这些建立一个复杂的关键帧动画,这一切都是为了对图层的一个独立作一个简单的动画。

幸运的是,有一个更好的解决方案:为了旋转图层,咱们能够对transform.rotation关键路径应用动画,而不是transform自己(清单8.9)。

清单8.9 对虚拟的transform.rotation属性作动画

复制代码
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add the ship CALayer *shipLayer = [CALayer layer]; shipLayer.frame = CGRectMake(0, 0, 128, 128); shipLayer.position = CGPointMake(150, 150); shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:shipLayer]; //animate the ship rotation CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation"; animation.duration = 2.0; animation.byValue = @(M_PI * 2); [shipLayer addAnimation:animation forKey:nil]; } @end
复制代码

 

结果运行的特别好,用transform.rotation而不是transform作动画的好处以下:

  • 咱们能够不经过关键帧一步旋转多于180度的动画。
  • 能够用相对值而不是绝对值旋转(设置byValue而不是toValue)。
  • 能够不用建立CATransform3D,而是使用一个简单的数值来指定角度。
  • 不会和transform.position或者transform.scale冲突(一样是使用关键路径来作独立的动画属性)。

transform.rotation属性有一个奇怪的问题是它其实并不存在。这是由于CATransform3D并非一个对象,它其实是一个结构体,也没有符合KVC相关属性,transform.rotation其实是一个CALayer用于处理动画变换的虚拟属性。

你不能够直接设置transform.rotation或者transform.scale,他们不能被直接使用。当你对他们作动画时,Core Animation自动地根据经过CAValueFunction来计算的值来更新transform属性。

CAValueFunction用于把咱们赋给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransform3D矩阵值。你能够经过设置CAPropertyAnimation的valueFunction属性来改变,因而你设置的函数将会覆盖默认的函数。

CAValueFunction看起来彷佛是对那些不能简单相加的属性(例如变换矩阵)作动画的很是有用的机制,但因为CAValueFunction的实现细节是私有的,因此目前不能经过继承它来自定义。你能够经过使用苹果目前已近提供的常量(目前都是和变换矩阵的虚拟属性相关,因此没太多使用场景了,由于这些属性都有了默认的实现方式)。

相关文章
相关标签/搜索