FacebookPop研究
研究人:*******
9月中旬
2014年秋季
/*该篇文章借鉴了许多前辈精心之作,因被借鉴文章太多,在此不一一声明了,如果有前辈看到自己被引用的部分可以联系添加备注,在此不胜感激*/
Facebook Pop研究
第一部分
Facebook Pop框架结构
何为Facebook Pop?
Pop是一个可扩展的iOS和OS X。除了基本的静态动画引擎,它支
持弹簧和衰减动态动画,使它有助于建立现实的,基于物理的相互作用。
该API允许快速整合现有的Objective-C代码库和任何对象任何属性的动
画。这是一个成熟和完善的测试框架,驱动所有的动画和过渡纸。(官方
定义)通俗的讲是开源的动画实现建构
Pop动画形式:
POPAnimationState 纪录动画状态
{
kPOPAnimationSpring,//弹簧效果动画
kPOPAnimationDecay,//衰减效果动画
kPOPAnimationBasic,//贝塞尔曲线运动效果动画
kPOPAnimationCustom,//自定义效果动画
}
POP的架构
POP目前由四部分组成:1.Animations;2.Engine;3.Utility;4.WebCore。
Animations: 定义pop支持的动画类型,并抽像各种动画的低层数据结构
Engine:组织动画运行的数据结构,核心是动化管理者,还包括了动画引擎所需要的可动画属性定义、动画追踪等内容
Utility: 封装Engine中用到的各种功能,包括数值运算,数据转换,运算等
WebCore: 这部分是苹果公司的代码,矩阵运算和贝赛尔曲线的,用于底层运算。
通过 Engine、Utility、WebCore三个基石,打造了Animations
为何用Pop?
POP动画极为流畅,Engine中的POPAnimator类使用CADisplayLink,
而CADisplayLink高达 60 FPS的特性,打造了一个游戏级的动画引擎。
通过CADisplayLink,Apple允许 App 的重绘速度设定到和屏幕刷新频率一致,由此可以获得非常流畅的交互动画.Pop中两个很好的地方:
1.除了基本的动画外,Pop还提供spring和decay动画;
2.你可以使用Pop对NSObject对象上的任何属性执行动画效果,而不仅仅是UIKit中声明的动画属性。
第二部分
——Pop代码阅读解析
Pop代码阅读解析:
注释:每个类有三个文件或者一到两个不等.在三个文件中private.h声明该类中所用变量,.h文件中定义属性,.mm文件具体实现.(此规律适合该代码的绝大部分类)
一.从POPAnimator深入解读Pop架构:
POPAnimator是整个Pop动画的管理者,在POPAnimator中定义了所有的动画事件(example:POPAnimatorItem,纪录了动画作用到的对象、动画key、动画对象等信息),提供了add、remove等操作Animation集合的方法,并提供了Observer和delegate等事件函数根据key为id添加动画集合的方法,以及移除全部动画集合或者是指定动画集合.此外还有为object添加动画关键值的数组调用方法,为动画add/remove 观察者的方法
1.PopAnimation:
定义了event数组,用数组来接收event,以及各种类型动画的公共属性和方法,是各种动画的父类,以下是Animation中用到的类
(1)POPGeometry:NSValue和Pop自定义类型的转换
(2)POPAnimationTracer:动画跟踪
<1>POPAnimationEvent:
定义结构体POPAnimationEventType声明POPAnimationTracer中的各个属性
<2>POPAnimationTracerInternal:
定义了initWithType:(POPAnimationEventType)方法来初始化event
(3)POPAnimationTracerInternal:
在该类中声明了read/write value值属性的函数,以及更新 ToValue,FromValue,Velocity,speed,mass等属性的函数
(4)POPAnimationRuntime:
包装函数vector,把vector转换成point,size,rect,color等,还有逆过程,即将point,size,rect,color封装成vector
功能类有:
(5)POPAction:
定义了ActionDisabler和ActionEnabler ,创建ActionEnabler时停止动画,销毁时重新运行
(6)POPSpringSolver:进行插值运算
扩展CAAnimation 和POPSpringAnimation方法,用到的类
用到的功能类有:
(1)POPMath:
该类用于数学计算,封装了UnitBezier,POPVector和其他数字计算其中POPVector是子类化的一个类,UnitBezier是用iOS自带的计算贝塞尔曲线方法确定运动路径
<1>POPVector :该类用于向量计算
<2>UnitBezier:结构体,初始化参数为两个控制点p1(p1x,p1y), p2(p2x,p2y),用于表示起始点为s(0,0),终止点为e(1,1),p1为第一控制点,p2为第二控制点的二次Bezier。
用途:重新实现系统的自带动画
//以下类只做简单的介绍,在应用指南中详解
3.POPBasicAnimation:(继承自POPPropertyAnimation)
从POPAnimation中子类化的类,该类具有基本动画的一切属性及方法,是pop四大核心之一
4.POPDecayAnimation:(继承自POPPropertyAnimation)
该类用来实现减速效果,有velocity速率属性,以及deceleration方向,duration持续时间
三.POPPropertyAnimation&POPCustomAnimation:
POPPropertyAnimation&POPCustomAnimation是POPAnimation子类化出来的两个类,POPPropertyAnimation的.mm文件中重写了set方法,在POPPropertyAnimation中使用了以下类
1.POPAnimatableProperty:
用于定义动画类型和属性,以及修改值.它定义了Layer,View,ScrollView,TableView,CollectionView,NavigationBar,ToolBar,TabBar,Label的动画属性,利用POP可以直接给控件添加动画
POPAnimatableProperty中的类族有:
POPConcreteAnimatableProperty
POPMutableAnimatableProperty
POPPlaceholderAnimatableProperty
POPStaticAnimatableProperty
设略到的功能类有:
(1)POPCGUtils:颜色转换函数,数组和点,point,size,rect,color等转换函数
(2)POPLayerExtras:图层矩阵变换,底层由TransformationMatrix实现
<1>TransformationMatrix:矩阵运算,初始化scal,rotate,translate,flipx,skew,applyPerspection等属性
<2>FloatConversion:
浮点数转换方法,提供double->float和double->CGFloat,
用途:服务于矩阵运算
总的来说,能够为开发者所用的仅有以下四个类
POPCustomAnimation
POPBasicAnimation
POPDecayAnimation
POPSpringAnimation
正是这四个类提供了强大的动画功能
//以下是从他人微博中的运行介绍,因本人不会组织语言,所以在此借用
2.动画执行流程:
1)POPAnimator是单例对象,初始化对象实例时注册把CADisplayLink到runloop,定时调用render,通过renderTime函数遍历所有正在执行的动画,使动画进行到下一帧,并刷新画面,直到所有动画推进完成。
2)动画状态的推进最终都是通过_POPAnimationState类和其子类的advance方法推进的,advance更新时间戳,updateAnimatable方法把state的状态变更到动画要作用的obj对象。
3)Spring、Decay、Basic都有各自实现的advance函数计算,而Custom方式不同,他是通过一个POPCustomAnimationBlock类型的回调来就算这一帧的值的。
3.动画算法
不同动画的advance都是如何推进的呢?
Basic:动画通过起点为(0,0),终点为(1,1)的Bezier曲线计算,x轴为时间,y轴为比例。advance方法中需要根据t值计算出对应的y值p,然后根据p的值进行插值计算(v=s+(e-s)*p),并进行了按照最小精度的四舍五入(源码设置的精度是1/(1000*总时间),动画时间越长,精度越高)。
Spring:这里的Vector4d不是真的向量,它可以表示point,size等结构,它起始就是个属性的数组而已,不要去想它的几何意义。
这个模型其实是一个简谐运动,
计算过程类似于弹簧,计算主要使用到这样几个量:
|---------|------|------轨迹
起点 当前点 间隔 平衡位置
p,表示当前点到弹簧平衡位置的距离 v,表示振子的速度 当前时间t 间隔时间dt
k弹簧的劲度系数 f= kl b阻力系数 m质量
_tp 瞬时值(偏移量)
_tv 瞬时速度
_ta 瞬时加速的
Derivative 导数~~
利用这些参数做插值算法,算出下一时刻的p和v
计算原理大致就是p2= p+ dt * v(因为v=p');v2 = v + dt * v'。
计算过程中运用了插值算法减小误差。说实话,没太看懂。希望这方面专家补充下。
问题:程序中计算dv用的是state.p*(-_k/_m)- state.v*(_b/_m);
正常dv=加速度a=f/m=f牵引/m-f阻力/m = state.p*(-_k/_m) - 阻力/m 这里库的作者可能认为阻力和速度成正比~~~,高中物理学的阻力是恒定的,但是模型运行的效果确实不错。
Decay:衰减函数,没有说明不知道为什么,应该和衰减公式有点关系,没弄清楚怎么变形的,下边是源码中的注释,v表示速度,dt时间间隔x位移
// v0 = v / 1000
// v = v0 * powf(deceleration, dt);
// v = v * 1000;
// x0 = x;
// x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 -deceleration)
Custom:通过自定义block刷新动画帧,更灵活的方式
第三部分
——Facebook POP使用
//以下部分参考官方指南
基本类型
SpringAnimation
ease-inease-out 这些可能你已经非常熟悉,这是动画的动作标配了,不过POP觉得只是这样显然太无聊,提供了两个非常不同于其的动画模式,第一个就是Spring Animation。
SpringAnimation 由诸多的复杂参数来控制,展现了一个非常风骚的姿势。
1.springBounciness弹簧弹力取值范围为[0,20],默认值为4
2.springSpeed弹簧速度,速度越快,动画时间越短[0, 20],默认为12,和springBounciness一起决定着弹簧动画的效果
3.dynamicsTension弹簧的张力
4.dynamicsFriction弹簧摩擦
5.dynamicsMass质量。张力,摩擦,质量这三者可以从更细的粒度上替代springBounciness和springSpeed控制弹簧动画的效果
Tension,Friction,Mass这三个参数的作用很微妙,需要你在示例程序里去仔细体会。
使用 Spring Animation的方式非常简单。
1. POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
2. anim.toValue = [NSValue valueWithCGPoint:CGPointMake(2.0, 2.0)];
3. anim.springBounciness = 4.0;
4. anim.springSpeed = 12.0;
5. anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
6. if (finished) {NSLog(@"Animation finished!");}};
通过 [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY]我们创建了一个二维平面上分别沿着 X和 Y 坐标轴进行缩放的动画。
因此我们要使用 toValue来告诉POP 我们希望分别缩放几倍,如果你不提供fromValue,那么POP将默认从当前的大小为依据进行缩放。值得一提的是,toValue这里的值要和动画作用的属性一样的结构。如果我们操作bounds,那么这里应该是[NSValue valueWithCGRect:CGRectMake(0.0, 0.0, 200.0,400.0)]。
completionBlock提供了一个Callback,动画的执行过程会不断调用这个block,finished这个布尔变量可以用来做动画完成与否的判断。
最后我们使用 pop_addAnimation来让动画开始生效,如果你想删除动画的话,那么你需要调用pop_removeAllAnimations。与 iOS自带的动画不同,如果你在动画的执行过程中删除了物体的动画,那么物体会停在动画状态的最后一个瞬间,而不是闪回开始前的状态。
DecayAnimation
DecayAnimation 就是POP提供的另外一个非常特别的动画,他实现了一个衰减的效果。这个动画有一个重要的参数velocity(速率),一般并不用于物体的自发动画,而是与用户的交互共生。这个和iOS7引入的UIDynamic非常相似,如果你想实现一些物理效果,这个也是非常不错的选择。
Decay的动画没有toValue只有fromValue,然后按照velocity来做衰减操作。如果我们想做一个刹车效果,那么应该是这样的。
1. POPDecayAnimation *anim = [POPDecayAnimation animWithPropertyNamed:kPOPLayerPositionX];
2. anim.velocity = @(100.0);
3. anim.fromValue = @(25.0);
4. //anim.deceleration = 0.998;
5. anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
6. if (finished) {NSLog(@"Stop!");}};
这个动画会使得物体从 X坐标的点25.0 开始按照速率100点/s做减速运动。这里非常值得一提的是,velocity也是必须和你操作的属性有相同的结构,如果你操作的是bounds,想实现一个水滴滴到桌面的扩散效果,那么应该是[NSValue valueWithCGRect:CGRectMake(0, 0,20.0, 20.0)]
如果 velocity是负值,那么就会反向递减。
deceleration(负加速度)是一个你会很少用到的值,默认是就是我们地球的0.998,如果你开发给火星人用,那么这个值你使用0.376会更合适。
PropertyAnimation & Basic Animation
POP号称可以对物体的任何属性进行动画,其背后就是这个Property Animation驱动。SpringAnimation和Decay Animation都是继承自这个类,接下来我们通过一个Counting Label的例子来展现这个神奇的能力。
与此同时我们也使用了 Basic Animation,经典的ease-in-out此刻发挥了重要的作用,因为我们并不需要计数器的数值进行回弹。
1. POPBasicAnimation *anim = [POPBasicAnimation animation];
2. anim.duration = 10.0;
3. anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
4. POPAnimatableProperty * prop = [POPAnimatableProperty propertyWithName:@"count" initializer:^(POPMutableAnimatableProperty *prop) { prop.readBlock = ^(id obj, CGFloat values[]) {
5. values[0] = [[obj description] floatValue];};
6. prop.writeBlock = ^(id obj, const CGFloat values[]) {
7. [obj setText:[NSString stringWithFormat:@"%.2f",values[0]]];};
8. prop.threshold = 0.01;}];
9. anim.property = prop;
10. anim.fromValue = @(0.0);
11. anim.toValue = @(100.0);
POPBasicAnimation的 timingFunction我们定义了动画的方式,慢开慢停。随后通过POPAnimatableProperty定义了POP 如何操作Label 上的数值。
readBlock中,obj就是我们的Label,values这个是动画作用的属性数组,其值必须是CGFloat,之前我们在Decay Animation中操作了bounds
那么 values[0],values1,values2,values3就分别对应CGRectMake(0, 0, 20.0, 20.0)的0, 0, 20.0, 20.0
这里我们只需要操作 Label上显示的文字,所以只需要一个参数。通过values[0] = [[obj description] floatValue]我们告诉 POP 如何获取这个值。
相应的我们通过 [obj setText:[NSStringstringWithFormat:@”%.2f”,values[0]]]告诉了 POP 如何改变Label 的属性。
threshold定义了动画的变化阀值,如果这里使用 1,那么我们就不会看到动画执行时候小数点后面的数字变化。到此为止,我们的Counting Label 就完成了,是不是超简单?
实战
PopUp& Decay Move
这个实例中我们介绍下如何将 Decay动画和用户的操作结合起来,实现一个推冰壶的效果。
首先我们给我们的物体添加个 UIPanGestureRecognizer的手势操作其,处理方式如下
1. case UIGestureRecognizerStateChanged: {
2. [self.popCircle.layer pop_removeAllAnimations];
3. CGPoint translation = [pan translationInView:self.view];
4. CGPoint center = self.popCircle.center;
5. center.x += translation.x;
6. center.y += translation.y;
7. self.popCircle.center = center;
8. [pan setTranslation:CGPointZero inView:self.popCircle];
9. break;
10. }
11. case UIGestureRecognizerStateEnded:
12. case UIGestureRecognizerStateCancelled: {
13. CGPoint velocity = [pan velocityInView:self.view];
14. [self addDecayPositionAnimationWithVelocity:velocity];
15. break;
16. }
当用户触摸这个冰壶的时候,所有动画会立刻停止,然后跟随用户的手指移动。通过 [panvelocityInView:self.view];我们获取了用户手指移动的速率然后在addDecayPositionAnimationWithVelocity中处理动画
1. POPDecayAnimation *anim = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPosition];
2. anim.velocity = [NSValue valueWithCGPoint:CGPointMake(velocity.x, velocity.y)];
当用户松开手之后,冰壶会依照地球的重力在低摩擦的状态下前进逐渐停止。如果想增大摩擦力,你可以把速率乘以一个摩擦系数。
FlyIn
在这个实例中,我们介绍下如何结合两个动画。实现一个像Path的卡片飞入的效果。
同样保留了 Decay Move的效果,你可以甩走这张卡片。
1. POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionY];
2. anim.fromValue = @-200;
3. anim.toValue = @(self.view.center.y);
4.
5. POPBasicAnimation *opacityAnim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
6. opacityAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
7. opacityAnim.toValue = @1.0;
8.
9. POPBasicAnimation *rotationAnim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerRotation];
10. rotationAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
11. rotationAnim.beginTime = CACurrentMediaTime() + 0.1;
12. rotationAnim.toValue = @(0);
首先把我们的冰壶变成卡片,旋转一点角度。这里需要注意的是,我们使用了duration来定义了Basic Animation的执行时间,beginTime来定义了动画的开始时间。beginTime接受的是一个以秒为单位的时间,所以我们使用了CACurrentMediaTime()获取了当前时间,然后加上了延迟时间。
Transform
这个实例是真的酷极了的效果,我们将实现一个用户点击后播放按钮转换为进度条容器的变形效果。
首先我们创建一个进度条,这个真是我最拿手的事情了。通过lineCap lineWidth我们调整进度条的样式,然后使用UIBezierPath定义了进度条的走向。
1. CAShapeLayer *progressLayer = [CAShapeLayer layer];
2. progressLayer.strokeColor = [UIColor colorWithWhite:1.0 alpha:0.98].CGColor;
3. progressLayer.lineWidth = 26.0;
4.
5. UIBezierPath *progressline = [UIBezierPath bezierPath];
6. [progressline moveToPoint:CGPointMake(25.0, 25.0)];
7. [progressline addLineToPoint:CGPointMake(700.0, 25.0)];
8. progressLayer.path = progressline.CGPath;
9.
10.
11. POPSpringAnimation *scaleAnim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
12. scaleAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(0.3, 0.3)];
13.
14. POPSpringAnimation *boundsAnim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds];
15. boundsAnim.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 800, 50)];
16. boundsAnim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
17. if (finished) {
18. UIGraphicsBeginImageContextWithOptions(self.popCircle.frame.size, NO, 0.0);
19. POPBasicAnimation *progressBoundsAnim = [POPBasicAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeEnd];
20. progressBoundsAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
21. progressBoundsAnim.toValue = @1.0;
22. progressBoundsAnim.completionBlock = ^(POPAnimation *anim, BOOL finished) {if (finished) {UIGraphicsEndImageContext();}};
23. [progressLayer pop_addAnimation:progressBoundsAnim forKey:@"AnimateBounds"];
24. }
25. };
首先是一起进行的 scale和 bounds 的变化效果,播放按钮将缩小然后改变外形成为进度条的容器,在变形结束后,我们触发进度条的动画。
这里我们使用UIGraphicsBeginImageContextWithOptions(self.popCircle.frame.size, NO, 0.0);开启了绘画上下文,动画结束后使用UIGraphicsEndImageContext();清空了绘画上下文。这个主要是影响了画板的大小。
这里我们没有使用UIGraphicsBeginImageContext()而是使用UIGraphicsBeginImageContextWithOptions()以此获取一个更清晰的绘图效果。