Pop VS Core Animation

优质
小牛编辑
132浏览
2023-12-01

当我们在本书前面讨论Core Animation的时候,以及它是如何在一个基本的水平上工作的,我结识了model layer和presentation layer的不同。模型层表示已知的准确的CALayer预加到动画上的属性。如果你添加一个动画到layer上,然后在动画进行到一半时问模型层它的属性是什么,答案是不会反映任何动画当前的内容的。如果你想要知道动画中实时的、运动中的layer的值,你就得去看表现层。而一旦动画完成后,表现层就会消失,所以如果你不想你的layer回到开始的位置,你就需要设置模型层的属性来匹配动画的最终状态。

这就是Core Animation的工作。这是苹果为了构建一个iPhone上用的动画框架在很多年前做出的一个基本的实现选择。而因为JNWSpringAnimation简单地为我们开发了一个依然是Core Animation对象的CAKeyframeAnimation,我们还是需要设置动画模型层的最终值来在完成时保持住。

Pop是完全不同的!

Pop不使用Core Animation来执行任何它提供的动画功能。不同之处在于它设置了一个特殊的时间对象来每1/60秒执行一次。那个每秒执行60次的代码会直接基于下一个你在弹簧动作中定义的位置更新任何你想要的属性。没有什么特别的、额外的layer添加到你的元素中去,Pop直接在UIView或者CALayer上改变属性,或者,有趣地在任何你想要的对象类型上改变。这意味着在动画中的任何时候,你都可以直接接触改变的属性的当前值而不用跳到任何表现层。并且更好的是,你不需要单独设置最终值让动画在那逗留,因为动画始终在实际的真实值上工作。

这个Pop用来支撑整个框架的时间对象是CADisplayLink,它可以看做是NSTimer的一个更高级版本,NSTimer是Mac游戏开发者常年用来在他们的Mac和iOS游戏中一帧帧运行代码的。NSTimer可以在你想要的任何时候调用任何你想调用的代码,不断地重复或者只调用一次。如果你想每5秒钟调用一次代码就可以使用NSTimer来做。或者如果你想要每秒调用代码60次,也可以用NSTimer来做,但当这么快地调用代码的时候(比如每次运动一点点像素,一步步地动画一个界面元素),这个时间对象就会失去准确的同步刷新频率,你可能会丢失一些帧,从而导致一些奇怪的短暂跳跃。

这就是CADisplayLink施展之处。CADisplayLink就是设计来避免这个问题的,因为它不是设置时间间隔,它一遍遍地调用你的方法的速率完全取决于屏幕的刷新频率。它随着屏幕的刷新来启动你的代码,这样你就有了最好的机会来每秒更新你的界面60次(平滑感知动作的时间)。这就是Pop用来将动画一像素一像素、1/60秒一次推动的方法。

让我们看一些简单的Pop动画时如何工作的。

// 添加我们的红球到界面上
UIView *redBall = [[UIView alloc] initWithFrame:CGRectMake(300, 300, 75, 75)];
redBall.backgroundColor = [UIColor redColor];
redBall.layer.cornerRadius = 75/2; // Half the width
[self.window addSubview:redBall];

POPSpringAnimation *scale =
    [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
scale.toValue = [NSValue valueWithCGPoint:CGPointMake(2, 2)];
scale.springBounciness = 20.0f; // Between 0-20
scale.springSpeed = 1.0f; // Between 0-20
[redBall pop_addAnimation:scale forKey:@"scale"];

这就是这个代码产生的动画。

很有弹性!我们已经谈论了很多关于如何用JNWSpringAnimation和Core Animation创建弹簧动画的内容,现在来看看Pop的方法。

POPSpringAnimation *scale =
    [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
scale.toValue = [NSValue valueWithCGPoint:CGPointMake(2, 2)];
scale.springBounciness = 20.0f; // Between 0-20
scale.springSpeed = 1.0f; // Between 0-20
[redBall pop_addAnimation:scale forKey:@"scaleAnimation"];

首先,我们创建了一个新的POPSpringAnimation对象。它被设计用 +animationWithPropertyNamed: 方法来初始化,获取参数来告诉Pop你想要动画什么属性。这非常像我们在JNWSpringAnimation中添加的关键路径值,但不是一个简单的字符串,Pop设计了很多值,这样你就不需要记住字符串。这里是一小部分Pop随时可以动画的属性。

  • kPOPViewAlpha——视图的透明度
  • kPOPViewFrame——视图的整体框架
  • kPOPViewScaleXY——视图的拉伸(X和Y轴)
  • kPOPViewBackgroundColor——视图的背景色
  • kPOPLayerCornerRadius——layer的角的度数
  • kPOPLayerRotation——layer的旋转度
  • kPOPLayerShadowRadius——layer下阴影的尺寸

所有的属性列表可以在Pop的GitHub找到。那是一个很长很长的清单,并且由于开发者一直在贡献,清单还在不断增长。

你可能注意到这些属性的命名有一些有趣的地方。我们有一个名为kPOPViewAlpha的属性,而另一个又名为kPOPLayerRotation。Pop酷的地方在于基于你传入的属性,你可以操作UIView的属性,也可以操作CALayer的属性。这完全取决于你,你只需要让Pop知道那一长串它支持的属性清单中你想要动画的是哪一个,无论它是一个view属性、layer属性或者任何类型的属性。Pop允许你更新任何你想要的类型的变量,甚至是与界面动画无关的。

我们设置了 toValue为 [NSValue valueWithCGPoint:CGPointMake(2, 2)] ,看起来可能会有点奇怪。为什么我们要将{2,2}这个点(我们的X和Y拉伸值)放到一个NSValue对象中去?好吧,这就是Pop工作的方式,它期待传到toValue参数中的是一个准确类型的值。而这个值得类型取决于我们添加的类型。它总是期待一个对象,在这个例子中,它想要一个CGPoint转化成的NSValue对象。不幸的是Pop的这个部分在文档中有点难懂,但随着开发者的贡献它也在变得更好。

至于要考虑的fromValue,我们在这个例子中没有设置它,因为Pop做了一些很酷的事情:如果你不设置它,它就会自动计算当前的开始值,并从这里开始。太赞了!

就如JNWSpringAnimation一样,你可以调整想要模仿的弹簧动作的属性。这里是上个例子中相关的部分。

scale.springBounciness = 20.0f; // Between 0-20
scale.springSpeed = 1.0f; // Between 0-20

Pop允许你调整弹簧的弹性和速度。每个值都可以从0到20.就如iOS 7中基于block的弹簧动画一样,这些值都是算入弹簧动作方程式的真实值的一个抽象。而不同于iOS 7的是,我认为Pop在抽象这些值时做的很棒,我还从没用Pop创建过一个看起来不自然或者违反物理法则的弹簧动作。

如果你想要调整动作方程使用的真实值,你也可以深入到一个更深的层次来操作它们。

scale.dynamicsFriction = 20;
scale.dynamicsMass = 1;
scale.dynamicsTension = 300;

这些值类似于JNWSpringAnimation中使用的值,但不完全一样,所以如果你想要准确地将一个JNWSpringAnimation变成Pop,就需要进行一些调整。幸运的是,springBounciness和springSpeed值在控制弹簧的动作上已经做得很好了,所以我经常就直接使用它们。

让我们看看弹性值的调整会如何影响动画。

这三个球的速度都是10,。红球的弹性是5,篮球是12,绿球是20。

最终,我们将动画添加到我们想要动画的对象上去。

[redBall pop_addAnimation:scale forKey:@"scaleAnimation"];

我们在视图上调用 -pop_addAnimation:forKey: 方法,并动画对象放入 pop_addAnimation:,然后将“scale”放入 forKey:。不同于JNWSpringAnimation和其他Core Animation的是,我们传入的键不需要匹配我们动画的属性。这个键只是视图上这个动画的一个唯一的名字,可以是你想要的任何值。如果你想要在运行的时候获取一个动画,你可以通过这个键来询问视图或者layer的Pop动画,这就是它的用处。

现在让我们来一次性添加一些不同的动画,每个都动画不同的属性。在我们展示代码之前,这里是它看起来的样子。

这个动画做了四件事情:视图的尺寸拉大,移动到了右边,旋转,并且还改变了背景色。这是我们添加的四个分开的动画,并且有四个分开的动画对象,每个表示一个不同的动画。

POPSpringAnimation *scale =
    [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.5, 1.5)];
scale.springBounciness = 15;
scale.springSpeed = 5.0f;
[orangeSquare pop_addAnimation:scale forKey:@"scale"];

POPSpringAnimation *move =
    [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
move.toValue = @(500);
move.springBounciness = 15;
move.springSpeed = 5.0f;
[orangeSquare.layer pop_addAnimation:move forKey:@"position"];

POPSpringAnimation *spin =
    [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
spin.toValue = @(M_PI*4);
spin.springBounciness = 15;
spin.springSpeed = 5.0f;
[orangeSquare.layer pop_addAnimation:spin forKey:@"spin"];

POPSpringAnimation *color =
    [POPSpringAnimation animationWithPropertyNamed:kPOPViewBackgroundColor];
color.toValue = [UIColor greenColor];
color.springBounciness = 15;
color.springSpeed = 5.0f;
[orangeSquare pop_addAnimation:color forKey:@"colorChange"];

我们使用了操作下面这些属性的动画:kPOPViewScaleXY、kPOPLayerPositionX、kPOPLayerRotation、kPOPViewBackgroundColor。两个动画时关于视图的,两个动画时关于layer的。

如果你观察一下我们设置为最终值的toValue变量,就可以看到一些不同的设置方法。如我之前所说,Pop一个有趣的(也有点烦人的?)方面在于Pop期望toValue改变的值取决于你要动画的属性。对于拉伸来说,我们已经说过了它想要一个NSValue对象。对于X位置动画,我们可以直接使用Objective-C的快捷方式@(500)来简单地给对象带来500.对于旋转,我们同样使用了特殊的@()语法。对于颜色我们设定了一个UIColor对象。所以你可以看到,因为Pop支撑了太多的动画属性,就有一些需要被理解的细微差别。我曾经混淆了NSValue包装的CGPoint`,并且盯着我的代码看了30秒才意识到它想要一些不同的值。

是时候用Pop来构建一些酷的东西了。