Facebook发布Paper之后,似乎还不满足于只是将其作为一个概念性产品,反而更进一步开源了其背后的动画引擎POP(https://github.com/facebook/pop),此举大有三年前发布的iOS UI框架Three20(https://github.com/facebookarchive/three20)的意味。而POP开源后不负Facebook的厚望,在GitHub上不足一个月的时间,就已拥有了6000多个Star,非常火爆。
POP背后的开发者是Kimon Tsinteris,他是Push Pop Press的联合创始人,曾在苹果担任高级工程师,并参与了iPhone和iPad上的软件研发(iPhone的指南针及地图)。2011年,Facebook收购了他的公司,此后他便加入了Facebook负责Facebook 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号称可以对物体的任何属性进行动画,其背后就是这个PropertyAnimation驱动。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()来清空绘画的上下文。这两个函数主要是影响画板的大小。 |