先来个效果图吧:
这是MMTweenAnimation
库实现的一个弹跳动画。MMTweenAnimation
基于Facebook的pop
动画库,它提供了10套自定义的动画曲线,分别是:Back
、Bounce
、Circ
、Cubic
、Elastic
、Expo
、Quad
、Quart
、Quint
、Sine
。具体的效果可以参考MMTweenAnimation。
在这里,我们主要来MMTweenAnimation
的具体实现及使用。
我们知道,动画实际上是许多帧静止的画面,以一定的速度连续播放,由于肉眼视觉残象产生的错觉,因此我们感觉画面是活动的。这就是动画的基本原理。所以,我们要做的就是按一定的速率去播放帧,在每一帧中计算曲线的路径,并将其绘制到界面上。这主要涉及到曲线的插值算法。
主要部件
MMTweenAnimation
的主体类主要有两个:MMTweenAnimation
和MMTweenFunction
。MMTweenFunction
类主要定义各种插值算法,MMTweenAnimation
主要是实现动画操作。
MMTweenFunction类
MMTweenFunction
类主要是实现各种插值算法。这些插值算法分别10类,即上面列出的10套动画。而每套根据不同的缓动方式,又分为EaseIn
、EaseOut
、EaseInOut
三种,因此MMTweenAnimation
库实际上是实现了30种动画。每个插值算法都实现为一个闭包函数,其定义如下:
|
|
而每个动画的插值都是根据数学公式算法出来的,我们以图例中的Bounce-EaseOut
动画为例,其实现如下:
|
|
计算出来的插值将会用于计算当前帧的终点值。
MMTweenAnimation类
MMTweenAnimation
是实现动画的主体类。这个类继承自pop
的POPCustomAnimation
,POPCustomAnimation
直接继承自PopAnimation
类,用于创建自定义动画的基类,它基本上是一个 display link
的方便的转换,来在动画的每一个tick
的回调block
中驱动自定义的动画。
MMTweenAnimation
定义了几个基本属性,如下所示:
|
|
而MMTweenAnimation
类最关键的是定义它的回调block
。MMTweenAnimation
类定义了一个类方法animation()
,在这个方法中,通过调用从父类继承来的便捷初始化方法
|
|
来创建一个MMTweenAnimation
对象。其实现如下所示:
|
|
其中动画回调的定义如下:
|
|
以上两个类便是MMTweenAnimation
的主要部件。
动画示例
有了主要部件,我们就来看看怎么去使用它。MMTweenAnimation
给了一个示例,其效果就是开头的图例。为此,MMTweenAnimation
定义了类MMPaintView
,这个类的主要目的就是绘制上面的曲线,其主要操作如下:
|
|
这个方法首先是将参数中的点(即每一帧计算出来的终点值)添加到对象的__dots
数组中,然后再通过__interpolateCGPointsWithCatmullRom
方法创建一条Bezier
曲线,最后调用setNeedsDisplay()
来重新绘制曲线。
我们先来看看这个点是如何获取到的。在MMAnimationController
类,我们定义动画对象时,设置了其动画回调,如下所示:
|
|
这个动画回调获取当前时间结束点的值,用于设置小红点的中心位置,同时将这个中心位置的值丢给MMPaintView
对象去生成Bezier
曲线。
动画渲染操作执行的时间点
知道了MMTweenAnimation
库的主要部件,我们现在来看看动画是如何被驱动的。我们在MMTweenAnimation
类的animation()
方法中,在动画回调的起始位置打个断点,运行一下程序,看看调用栈,如下所示:
可以看到在Run Loop
中执行了一个观察者回调,在这个回调中调用了POPAnimator
对象的_scheduleProcessPendingList
方法的一个block
回调,一直追溯到我们的动画操作。也就是说,是在Run Loop
的某个时刻执行了一次动画的渲染。
我们再从代码入手,来看看动画执行代码是什么时候添加到Run Loop中的。在MMAnimationController
的viewDidAppear
方法中,有如下调用:
|
|
其中pop_addAnimation
方法是POPAnimator
类中定义的。顺着代码,我们最终可以找到_scheduleProcessPendingList
的定义,其实现如下:
|
|
可以看到在这个方法中创建了一个Run Loop
的观察者,这个观察者在Run Loop
的kCFRunLoopBeforeWaiting
或kCFRunLoopExit
阶段时会执行监听回调处理函数。回调函数中调用了_processPendingList
方法,然后从调用栈里面可以看到,一直会执行到MMTweenAnimation
的动画闭包中,即我们打断点的地方。
OK,动画渲染时间点找着了,那整个流程就可以完整拼接起来了。
小结
MMTweenAnimation
的实现并不复杂,只要了解了动画的基本原理和其中的插值算法,再加上一些pop
动画的基础知识,基本上就OK了。要想做出很牛B的动画,还是需要大量的数学知识。其实在MMTweenAnimation
库中,除了那10套插值算法外,在MMPaintView
类中,计算Bezier
的控制点时,还用到了Catmull-Rom样条与Hermite样条,大家有兴趣可以研究一下。
MMTweenAnimation
初始源码是Objective-C
实现的,我将它用Swift
重写了一遍,并放在github上,地址是MMTweenAnimation-Swift,有兴趣可以看一下。
本想放在知识小集中,但由于篇幅稍长,所以独立成篇。