Facebook Pop 代码解析及使用指南(部分代码参考第三方研究及官方定义)

 

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.Animations2.Engine3.Utility4.WebCore

 

Animations: 定义pop支持的动画类型,并抽像各种动画的低层数据结构

 

Engine:组织动画运行的数据结构,核心是动化管理者,还包括了动画引擎所需要的可动画属性定义、动画追踪等内容

 

Utility: 封装Engine中用到的各种功能,包括数值运算,数据转换,运算等

 

WebCore: 这部分是苹果公司的代码,矩阵运算和贝赛尔曲线的,用于底层运算。

 

通过 EngineUtilityWebCore三个基石,打造了Animations

 

为何用Pop?

POP动画极为流畅,Engine中的POPAnimator类使用CADisplayLink

CADisplayLink高达 60 FPS的特性,打造了一个游戏级的动画引擎。

通过CADisplayLinkApple允许 App 的重绘速度设定到和屏幕刷新频率一致,由此可以获得非常流畅的交互动画.Pop中两个很好的地方:

1.除了基本的动画外,Pop还提供springdecay动画;

2.你可以使用PopNSObject对象上的任何属性执行动画效果,而不仅仅是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:NSValuePop自定义类型的转换

      

      (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:进行插值运算

       
   2.POPAnimationExtras:

扩展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&POPCustomAnimationPOPAnimation子类化出来的两个类,POPPropertyAnimation.mm文件中重写了set方法,POPPropertyAnimation中使用了以下类 

 

 1.POPAnimatableProperty:

用于定义动画类型和属性,以及修改值.它定义了Layer,View,ScrollView,TableView,CollectionView,NavigationBar,ToolBar,TabBar,Label的动画属性,利用POP可以直接给控件添加动画

 

      POPAnimatableProperty中的类族有:

              POPConcreteAnimatableProperty

        POPMutableAnimatableProperty

            POPPlaceholderAnimatableProperty

              POPStaticAnimatableProperty

设略到的功能类有:

      (1)POPCGUtils:颜色转换函数,数组和点,pointsizerectcolor等转换函数

      (2)POPLayerExtras:图层矩阵变换,底层由TransformationMatrix实现

          <1>TransformationMatrix:矩阵运算,初始化scal,rotate,translate,flipx,skew,applyPerspection等属性

          <2>FloatConversion

    

浮点数转换方法,提供double->floatdouble->CGFloat

      用途:服务于矩阵运算

 

总的来说,能够为开发者所用的仅有以下四个类

 

POPCustomAnimation

POPBasicAnimation

POPDecayAnimation

POPSpringAnimation

正是这四个类提供了强大的动画功能

 

//以下是从他人微博中的运行介绍,因本人不会组织语言,所以在此借用

2.动画执行流程:

   1)POPAnimator是单例对象,初始化对象实例时注册把CADisplayLinkrunloop,定时调用render,通过renderTime函数遍历所有正在执行的动画,使动画进行到下一帧,并刷新画面,直到所有动画推进完成。

   2)动画状态的推进最终都是通过_POPAnimationState类和其子类的advance方法推进的,advance更新时间戳,updateAnimatable方法把state的状态变更到动画要作用的obj对象。

   3)SpringDecayBasic都有各自实现的advance函数计算,而Custom方式不同,他是通过一个POPCustomAnimationBlock类型的回调来就算这一帧的值的。

 

3.动画算法

不同动画的advance都是如何推进的呢?

Basic:动画通过起点为(00),终点为(11)的Bezier曲线计算,x轴为时间,y轴为比例。advance方法中需要根据t值计算出对应的yp,然后根据p的值进行插值计算(vs+(es*p),并进行了按照最小精度的四舍五入(源码设置的精度是1/1000*总时间),动画时间越长,精度越高)。

 

Spring:这里的Vector4d不是真的向量,它可以表示pointsize等结构,它起始就是个属性的数组而已,不要去想它的几何意义。

这个模型其实是一个简谐运动,

计算过程类似于弹簧,计算主要使用到这样几个量:

 |---------|------|------轨迹

 起点            当前点  间隔 平衡位置

p,表示当前点到弹簧平衡位置的距离  v,表示振子的速度  当前时间t   间隔时间dt

k弹簧的劲度系数 f kl     b阻力系数    m质量   

   _tp 瞬时值(偏移量)

   _tv 瞬时速度

   _ta 瞬时加速的

 

   Derivative 导数~~

   利用这些参数做插值算法,算出下一时刻的pv

   计算原理大致就是p2 p+ dt * v(因为vp');v2 = v + dt * v'

   计算过程中运用了插值算法减小误差。说实话,没太看懂。希望这方面专家补充下。

   问题:程序中计算dv用的是state.p*(-_k/_m)- state.v*(_b/_m);

   正常dv=加速度af/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质量。张力,摩擦,质量这三者可以从更细的粒度上替代springBouncinessspringSpeed控制弹簧动画的效果 

 

TensionFrictionMass这三个参数的作用很微妙,需要你在示例程序里去仔细体会。

 

使用 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,动画的执行过程会不断调用这个blockfinished这个布尔变量可以用来做动画完成与否的判断。

 

最后我们使用 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驱动。SpringAnimationDecay Animation都是继承自这个类,接下来我们通过一个Counting Label的例子来展现这个神奇的能力。

与此同时我们也使用了 Basic Animation,经典的easeinout此刻发挥了重要的作用,因为我们并不需要计数器的数值进行回弹。

  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就是我们的Labelvalues这个是动画作用的属性数组,其值必须是CGFloat,之前我们在Decay Animation中操作了bounds

 

那么 values[0]values1values2values3就分别对应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()以此获取一个更清晰的绘图效果。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值