非死book POP,迈向大师操作之路
非死book 发布 Paper 之后,似乎还不满足于只是将其作为一个概念性产品,反而更进一步开源了其背后的动画引擎 POP(https://github.com/非死book/pop),此举大有三年前发布的 iOS UI 框架 Three20(https://github.com/非死bookarchive/three20)的意味。而 POP 开源后不负 非死book 的厚望,在 GitHub 上不足一个月的时间,就已拥有了 6000 多个 Star,非常火爆。
POP 背后的开发者是 Kimon Tsinteris,他是 Push Pop Press 的联合创始人,曾在苹果担任高级工程师,并参与了 iPhone 和 iPad 上的软件研发(iPhone 的指南针及地图)。2011 年,非死book 收购了他的公司,此后他便加入了 非死book 负责 非死book iOS 版本的开发。
如果你打开 Push Pop Press 开发的《AI Gore》这款 App,就会发现它的交互和动画与 Paper 几乎如出一辙,原因就在于,它们都是由 Kimon Tsinteris 开发的。由于不满足于苹果自身动画框架的单调,Push Pop Press 致力于创造一个逼真的、充满物理效应的体验。POP 就是在这个理念下催生出来的新一代成果。
POP 使用 Objective-C++编写。Objective-C++是对 C++ 的扩展,就像 Objective-C 是C的扩展一样。而至于为什么他们用 Objective-C++而不是纯粹的 Objective-C,原因在于他们更喜欢 Objective-C++的语法特性所提供的便利。
POP 的架构
POP 目前由四个部分组成(如图 1 所示),即 Animations、Engine、Utility、WebCore。
图 1 POP 架构图
POP 动画极为流畅,其秘密就在于这个引擎中的 POPAnimator。POP 通过 CADisplayLink 让动画实现了 60 FPS 的流畅效果,打造了一个游戏级的动画引擎。
CADisplayLink 是类似 NSTimer 的定时器,不同之处在于,NSTimer 用于我们定义任务的执行周期及资料的更新周期,它的执行受 CPU 的阻塞所影响。而 CADisplayLink 则用于定义画面的重绘和动画的演变,它的执行是基于 Frames 的间隔。通过 CADisplayLink,苹果允许开发者将 App 的重绘速度设定到与屏幕刷新频率一致。因此开发者可以获得非常流畅的交互动画,这项技术的应用在游戏中非常常见,著名的 Cocos-2d 引擎也用到了这个重要的技术。
WebCore 里包含了一些从苹果的开源的网页渲染引擎里拿到的源文件(http://opensource.apple.com/source/WebCore),它与 Utility 里的组件一并为 POP 的各项复杂计算提供了基本支持。因此,通过 Engine、Utility、WebCore 三个基石,打造了 Animations。
POPAnimation 有着与 CALayer 非常相似的 API。如果你知道 CALayer 的动画 API,那么你对下面的接口一定非常熟悉。说到这里,想必你一定开始迫不及待地想试试 POP 了(因篇幅所限,下面的代码并不是完整代码,你可以到 https://github.com/kevinzhow/pop-handapp 获取示例 App)。
基本类型
· Spring Animation
图 2 默认的两种动画模式以及他们的动画节奏
POP 默认提供了两个非常特别的动画模式,第一个就是 Spring Animation(如图 2 所示),另一个是 Decay Animation。让我们先来看看 Spring Animation,控制其动画效果的主要参数包括:
· Bounciness 反弹,影响动画作用的参数的变化幅度;
· Speed 速度;
· Tension 拉力,影响回弹力度及速度;
· Friction 摩擦力,开启后,动画会不断重复,并且幅度逐渐削弱,直到停止;
· Mass 质量,细微地影响动画的回弹力度和速度。
实际上,Tension、Friction、Mass 这三个参数的作用很微妙,需要在示例程序中仔细体会。使用 Spring Animation 的方式非常简单,如代码 1 所示。
代码1
通过[POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY]我们创建了一个在二维平面上分别沿着X轴和Y轴进行缩放的动画。
下面我们介绍三个重要的参数。
· fromValue 将告诉 POP 物体被动画操作的属性从什么数值开始运行。如果不提供 fromValue,那么 POP 将默认使用当前数值。在这个例子中,就默认使用当前的比例。
· toValue 是我们希望动画结束后,物体被动画操作的属性停留在什么值上,在这个例子中,toValue 告诉了 POP,我们希望沿着X轴和Y轴各缩放几倍。
· completionBlock 提供了一个 Callback,动画的执行过程会不断调用这个 block,finished 这个布尔变量可以用来做动画完成与否的判断。
值得一提的是,这里 toValue 和 fromValue 的值应该和动画所作用的属性是一样的数据结构。例如,如果我们的操作对象是 bounds,那么这里的 toValue 则应该是[NSValue valueWithCGRect:] 。
最后,我们使用 pop_addAnimation 来让动画开始生效,如果想删除动画的话,那么需要调用 pop_removeAllAnimations。
与 iOS 自带的动画不同,如果你在动画的执行过程中删除了物体的动画,那么物体会停在动画状态的最后一个瞬间,而不是闪回开始前的状态。
· Decay Animation
Decay Animation 可以实现衰减的动画效果。这个动画有一个重要的参数即 velocity(速率),这个参数一般并不用于物体的自发动画,而是与用户的交互共生。这和 iOS 7 引入的 UIDynamic 非常相似,如果你想实现一些物理效果,Decay Animation 也是不错的选择。
Decay 的动画没有 toValue 只有 fromValue,以 fromValue 作为原始值,按照 velocity 来做衰减操作。如果我们想做一个刹车效果,则可以像代码 2 这样操作:
代码2
这个动画会使得物体从X坐标的 25.0 开始做 100 点/秒的减速运动。如果 velocity 里的数字是负值,那么你的动画就会反方向执行动画效果。这里非常值得一提的是,velocity 也是必须和你操作的属性有相同的数据结构,如果你操作的是 bounds,想实现一个水滴滴到桌面的扩散效果,那么 velocity 则应该是[NSValue valueWithRect:CGRectMake (0, 0, 20, 20)]。
deceleration(负加速度)是一个很少用到的值,它影响动画被重力影响的效果。默认值就是我们地球的重力加速度 0.998。如果你程序里的动画开发给火星人看,那么使用 0.376 这个值会更合适。
· Property Animation 和 Basic Animation
POP 号称可以对物体的任何属性进行动画,其背后就是这个 Property Animation 驱动。Spring Animation 和 Decay Animation 都是继承自这个类,接下来我们通过一个 Counting Label 的例子来演示 Property Animation 的神奇能力。在这个动画中,我们也使用了 Basic Animation,动画模式是经典的 ease-in-out,不使用 Spring Animation 是因为我们并不需要计数器的数值进行回弹,如代码 3 所示。
代码3
通过 POPBasicAnimation 的 timingFunction 我们定义了动画的展现方式——渐入渐出。随后通过 POPAnimatableProperty 来定义 POP 如何操作 Label 上的数值。
这里我们需要注意两个函数,readBlock 和 writeBlock。readBlock 定义了动画如何获取要操作的属性数值,writeBlock 定义了动画如何修改要操作的属性数值。在这两个函数中,obj 就是我们的 Label,values 是动画所操作的属性数组,其值必须是 CGFloat。
你可能会问,什么是动画所操作的属性数组?回顾之前我们在 Decay Animation 中操作的 bounds 内容,可以看出 values[0]、values[1]、values[2]、values[3]分别对应了 CGRectMake(0, 0, 20.0, 20.0)的0、0、20.0、20.0。这里我们需要操作 Label 上显示的文字,所以只需要一个 values[0]属性即可。
通过 values[0]=[[obj description] floatValue]我们告诉 POP 如何获取这个值。相应地,我们通过[obj setText: [NSString stringWithFormat:@"%.2f",values[0]]],告诉 POP 如何改变 Label 的属性。
threshold 定义了动画的变化阀值,如果这里使用1,那么我们就不会看到动画执行时小数点后面的数字变化。
到这里,我们的 Counting Label 就完成了,是不是超简单?
实战
· PopUp 和 Decay Move
这个实例中,我将介绍一下如何将 Decay 动画和用户的手势操作结合起来,实现一个推冰壶的效果。手势的处理方式如代码 4 所示。
代码4
当用户触摸这个冰壶时,所有动画会立刻停止,然后冰壶会跟随用户的手指移动。在用户松开冰壶时,通过 [pan velocityInView:self.view]我们获取了用户手指移动的速率,在 addDecayPositionAnimationWithVelocity 中生成动画,如代码 5 所示。
代码5
动画生效后,冰壶就会在低摩擦的状态下前进并逐渐停止。如果想增大摩擦力,则可以将速率乘以摩擦系数。
· Fly In
在这个实例中,我将介绍一下如何将两个动画相结合,实现一个像 Path 中卡片飞入的效果。如代码 6 所示。
代码6
第一个 Spring Animation 实现了卡片下落的效果,第二个 Basic Animation 实现了卡片的渐入效果,而最后的一个 Basic Animation 则实现了卡片倾斜的效果。
这里需要注意的是,我们使用了 duration 来定义 Basic Animation 的执行时间,并用 beginTime 来定义动画的开始时间。beginTime 接受的是一个以秒为单位的时间,所以我们使用了 CACurrentMediaTime ()来获取当前的时间,在此之上增加上了期望动画延迟的时间。
· Transform
这个实例真的酷极了,我们将实现一个用户点击后播放按钮转换为进度条容器的变形效果。首先创建一个进度条,通过 lineCap lineWidth 调整进度条的样式,然后使用 UIBezierPath 来定义进度条的走向,如代码 7 所示。
代码7
代码 8 就是实现变形的代码。从这段代码不难看出,scale 和 bounds 的变化效果是一起进行的。这时,播放按钮将缩小,然后改变外形成为进度条的容器。在变形结束后,将触发进度条的动画。
代码8
这里我们使用 UIGraphicsBeginImageContext-WithOptions ()去开启绘画上下文,动画结束后使用 UIGraphicsEndImageContext ()来清空绘画的上下文。这两个函数主要是影响画板的大小。
作者周楷雯,广州趣拼科技创始人。钟情 iOS,粗通 Rails,偶尔做做设计,总是被有趣的人和美好的事所吸引。