2

前言

应用中的动画根据需求肯定不可能是一成不变的。

当动画进行不同的状态切换的时候我们需要接下来要做什么。与见见单单的两个屏幕画面跳转不同,动画需要告诉用户什么即将到来而又要到哪里去。

键盘事件很好地解释了动画是对用户具有解释性的属性。同样视图控制器切换也可以通过大致的方向给用户提示接下来他/她要去前往哪个界面。附带细微得弹簧与碰撞效果提升整个感觉效果让APP更具有生气。

动画对你的APP而言是一个很好地介绍方式。当然需要懂动画的基本原理后,那么你才能更好掌握如何去整动画。

源头

尽管高级UIKit框架中的高级API可以实现很多动画,但是还是从CALayer层入手,因为这正式animation操作的对象。

这里要知道animation加在一个视图的layer层不是直接通过改变其属性来展示动画效果的。

相反,Core Animation维护了两套layer层级:数据模型layer层树和表现layer层树。前者存放层的状态而后者展示动画值。

试想下一个视图的淡出动画。如果你在动画的任意时间点去查看层透明度值,你将得不到与屏幕所对应的透明度值。相反若你去表现层中获取将能获取到正确的透明度值。

当然你不能直接对表现层进行直接的赋值,它可以有效的用来创建新的动画或在动画期间能进行很好的互动。

通过调用-[CALayer presentationLayer] 与 -[CALayer modelLayer],可你可以在两种层级结构中轻易切换。

基本动画

动画很多很多场景基本是视图的一个属性值变化从一个值到另外一个值,思考如下栗子:
图片描述
上述火箭的X坐标从77变化到了455,已经超过了能展示的区间,为了能填充所有得动画帧,我们需要确定火箭在任意时间点得位置。通常用线性表达来求得火箭X坐标与时间t的关系:
图片描述
使用CABasicAnimation,根据如下代码可以实现上述描述:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

[rocket.layer addAnimation:animation forKey:@"basic"];

主要是用KVC,获取要修改的属性position.x。这是CA封好的一个使用起来比较方便得地方。。。(确实比较方便)

因为默认情况下animation不会直接修改表现层,并在动画结束的时候被移除掉。一旦动画被移除了,表现层将会回到初始的状态。

解决方案有两个:

第一种方式是直接更新数据层的属性(推荐),因为这么做使动画完全可选。

一旦动画结束并且从layer层移除,那么表现层将会与数据层契合,正好符合动画的最后一帧。

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

[rocket.layer addAnimation:animation forKey:@"basic"];

rocket.layer.position = CGPointMake(455, 61);

或者你可以通过给动画的fillMode赋值为kCAFillModeForwards同时将removedOnCompletion设置为NO来让动画保持最后一帧的状态。但是动画结束保持表现层与数据层的统一是一个良好得习惯,所以这种方法使用的时候要十分注意。

Andy Matuschak指出如果保持完成的动画将渲染一些不必要的外形带来额外的消耗。

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

animation.fillMode = kCAFillModeForward;
animation.removedOnCompletion = NO;

[rectangle.layer addAnimation:animation forKey:@"basic"];

非常有必要说明下每当我们创建一个动画对象,它将会为添加到layer层。这个特点我们需要记在心里因为当有多个视图的时候我们可以进行重用。如果我们第二个火箭想再第一个火箭动画完成后进行发射:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.byValue = @378;
animation.duration = 1;

[rocket1.layer addAnimation:animation forKey:@"basic"];
rocket1.layer.position = CGPointMake(455, 61);

animation.beginTime = CACurrentMediaTime() + 0.5;

[rocket2.layer addAnimation:animation forKey:@"basic"];
rocket2.layer.position = CGPointMake(455, 111);

因为是copy的缘故,上述animation的修改只会影响到后面添加该animation的视图layer层对象。

CABasicAnimation中的byValue属性效果是当你动画结束的时候变化的属性值加上某个值(byValue)。这样的好处使得动画更容易被复用。这样我们不用特别去控制起始值和终止值。

多场景动画

就是两步以上得动画。。。这时候我们可以使用泛型动画CAKeyframeAnimation

Keyframes让我们可以随意的设定我们动画的关键位置点,并自动为我们填充关键位置点之间的动画效果。

举个输入密码,密码验证错误得动画效果:
图片描述

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position.x";
animation.values = @[ @0, @10, @-10, @10, @0 ];
animation.keyTimes = @[ @0, @(1 / 6.0), @(3 / 6.0), @(5 / 6.0), @1 ];
animation.duration = 0.4;

animation.additive = YES;

[form.layer addAnimation:animation forKey:@"shake"];

还是KVC找出动画要变动的属性,通过animation的value属性提供了中间变化点得值数组外加一个对应动画变动时间点的时间数组。(这里略了一段)

沿着指定轨迹的动画(外国佬的东西总是滔滔不绝。。无脑翻译都很累啊。。)

这里先吐槽了下沿着复杂轨迹走需要保存好多好多的坐标啊。。。然后说尼玛幸好CAKeyframeAnimation提供了便捷的处理方式。

举个栗子:

CGRect boundingRect = CGRectMake(-150, -150, 300, 300);

CAKeyframeAnimation *orbit = [CAKeyframeAnimation animation];
orbit.keyPath = @"position";
orbit.path = CFAutorelease(CGPathCreateWithEllipseInRect(boundingRect, NULL));
orbit.duration = 4;
orbit.additive = YES;
orbit.repeatCount = HUGE_VALF;
orbit.calculationMode = kCAAnimationPaced;
orbit.rotationMode = kCAAnimationRotateAuto;

[satellite.layer addAnimation:orbit forKey:@"orbit"];

通过调用类方法我们创建了一个圆形路径用来当做动画路径。

使用calculationMode用来控制每个时间源的帧动画。设置成kCAAnimationPaced来保证匀速。

设置rotationMode来保证动画贴合轨迹进行适度旋转。否则动画如下:

时间源函数

在现实生活中是需要加速以及减速后进入匀速状态,动画需要尽量的真实需要引入时间参数方程来描述加速与减速的过程。

图片描述
上述已经展示过最简单得加速方式:线性加速。在CA中这个方程使用CAMediaTimingFunction类。
图片描述

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @50;
animation.toValue = @150;
animation.duration = 1;

animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

[rectangle.layer addAnimation:animation forKey:@"basic"];

rectangle.layer.position = CGPointMake(150, 0);

CA还内置了一些其他时间源方程的动画效果。。此处省略N个字。。。

另外介绍下还可以根据你想要添加的控制点自定义自己的时间源方程:+functionWithControlPoints::::
图片描述
代码如下:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.5:0:0.9:0.7];

[rocket.layer addAnimation:animation forKey:@"basic"];

rocket.layer.position = CGPointMake(150, 0);

注:贝塞尔曲线是用来画平滑曲线用的。。。
贝塞尔曲线中的几个控制点被传入+functionWithControlPoints::::(参数范围是0到1),时间源方程会根据路径调整当前的速度。

然后作者自己封了一个库来满足更复杂的动画效果包括了反弹效果:
图片描述

RBBTweenAnimation *animation = [RBBTweenAnimation animation];
animation.keyPath = @"position.y";
animation.fromValue = @50;
animation.toValue = @150;
animation.duration = 1;

animation.easing = RBBEasingFunctionEaseOutBounce;

组动画

为了达到某些复杂的效果,可能需要在同一时刻改变一系列得属性值。
比如同时改变视图的位置,翻转以及Z轴得相对位置。此时使用CAAnimationGroup,我们可以这么写:

CABasicAnimation *zPosition = [CABasicAnimation animation];
zPosition.keyPath = @"zPosition";
zPosition.fromValue = @-1;
zPosition.toValue = @1;
zPosition.duration = 1.2;

CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation];
rotation.keyPath = @"transform.rotation";
rotation.values = @[ @0, @0.14, @0 ];
rotation.duration = 1.2;
rotation.timingFunctions = @[
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
];

CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
position.keyPath = @"position";
position.values = @[
    [NSValue valueWithCGPoint:CGPointZero],
    [NSValue valueWithCGPoint:CGPointMake(110, -20)],
    [NSValue valueWithCGPoint:CGPointZero]
];
position.timingFunctions = @[
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
];
position.additive = YES;
position.duration = 1.2;

CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = @[ zPosition, rotation, position ];
group.duration = 1.2;
group.beginTime = 0.5;

[card.layer addAnimation:group forKey:@"shuffle"];

card.layer.zPosition = 1;

使用组动画的好处在于我们能见分散的动画用一个对象来控制。这样的好处在我们工厂生产出来的对象时候可以在多处复用我们的代码。同时我们可以用组动画在同一时刻进行时间源函数控制。

CA之外

略。。。
大概是扯皮ios7中的运行时约束限制
以及FB的Pop开源介绍


Cruise_Chan
729 声望71 粉丝

技能树点歪了...咋办