聊聊动画引擎 pop

来源:互联网 发布:百度霸屏js 编辑:程序博客网 时间:2024/05/16 10:51

聊聊动画引擎 pop



iOS可以通过CADisplayLink实现自定义动画引擎,pop就是基于此实现的,而且比原生Core Animation更强大好用。譬如当ViewController侧滑返回的时候,系统会将Core Animation的动画会停止,而基于CADisplayLink实现的动画则不会停止,因而可以实现类似网易云音乐从播放页侧滑时hold住专辑封面图旋转的效果。


八一八魔性的pop


1、实用的宏


#define POP_ARRAY_COUNT(x) sizeof(x) / sizeof(x[0])

 

#define FB_PROPERTY_GET(stype, property, ctype) \

-(ctype)property{\

  return((stype *)_state)->property;\

}

 

#define FB_PROPERTY_SET(stype, property, mutator, ctype, ...) \

-(void)mutator(ctype)value{\

  if(value == ((stype *)_state)->property)\

    return;\

  ((stype *)_state)->property = value;\

  __VA_ARGS__\

}

 

#define FB_PROPERTY_SET_OBJ_COPY(stype, property, mutator, ctype, ...) \

-(void)mutator(ctype)value{\

  if(value == ((stype *)_state)->property)\

    return;\

  ((stype *)_state)->property = [valuecopy];\

  __VA_ARGS__\

}


2、判定值的数据类型


pop定义了支持的值的数据类型


constPOPValueTypekPOPAnimatableSupportTypes[10] = {kPOPValueInteger,kPOPValueFloat,kPOPValuePoint,kPOPValueSize,kPOPValueRect,kPOPValueEdgeInsets,kPOPValueColor,kPOPValueSCNVector3,kPOPValueSCNVector4};


通过@encode指令,将给定类型编码的内部字符串与objcType对比,得到值的数据类型


staticboolFBCompareTypeEncoding(constchar*objctype,POPValueTypetype)

{

  switch(type)

  {

    case kPOPValueFloat:

      return(strcmp(objctype,@encode(float)) == 0

              ||strcmp(objctype,@encode(double)) == 0

              );

 

    case kPOPValuePoint:

      return(strcmp(objctype,@encode(CGPoint)) == 0

#if !TARGET_OS_IPHONE

              ||strcmp(objctype,@encode(NSPoint)) == 0

#endif

              );

 

    case kPOPValueSize:

      return(strcmp(objctype,@encode(CGSize)) == 0

#if !TARGET_OS_IPHONE

              ||strcmp(objctype,@encode(NSSize)) == 0

#endif

              );

 

    case kPOPValueRect:

      return(strcmp(objctype,@encode(CGRect)) == 0

#if !TARGET_OS_IPHONE

              ||strcmp(objctype,@encode(NSRect)) == 0

#endif

              );

    case kPOPValueEdgeInsets:

#if TARGET_OS_IPHONE

      returnstrcmp(objctype,@encode(UIEdgeInsets)) == 0;

#else

      returnfalse;

#endif

 

    case kPOPValueAffineTransform:

      returnstrcmp(objctype,@encode(CGAffineTransform)) == 0;

 

    case kPOPValueTransform:

      returnstrcmp(objctype,@encode(CATransform3D)) == 0;

 

    case kPOPValueRange:

      returnstrcmp(objctype,@encode(CFRange)) == 0

      ||strcmp(objctype,@encode(NSRange)) == 0;

 

    case kPOPValueInteger:

      return(strcmp(objctype,@encode(int)) == 0

              ||strcmp(objctype,@encode(unsignedint)) == 0

              ||strcmp(objctype,@encode(short)) == 0

              ||strcmp(objctype,@encode(unsignedshort)) == 0

              ||strcmp(objctype,@encode(long)) == 0

              ||strcmp(objctype,@encode(unsignedlong)) == 0

              ||strcmp(objctype,@encode(longlong)) == 0

              ||strcmp(objctype,@encode(unsignedlonglong)) == 0

              );

 

    case kPOPValueSCNVector3:

#if SCENEKIT_SDK_AVAILABLE

      returnstrcmp(objctype,@encode(SCNVector3)) == 0;

#else

      returnfalse;

#endif

 

    case kPOPValueSCNVector4:

#if SCENEKIT_SDK_AVAILABLE

      returnstrcmp(objctype,@encode(SCNVector4)) == 0;

#else

      returnfalse;

#endif

 

    default:

      returnfalse;

  }

}


3、将值的数据类型标准化为Vector


举个CGRect类型的例子:


case kPOPValueRect:

      vec = Vector::new_cg_rect([valueCGRectValue]);

 

Vector*Vector::new_cg_rect(constCGRect &r)

  {

    Vector*v = newVector(4);

    v->_values[0] = r.origin.x;

    v->_values[1] = r.origin.y;

    v->_values[2] = r.size.width;

    v->_values[3] = r.size.height;

    returnv;

  }


通过Vector的两个参数size_t _count;、CGFloat *_values;将给定的类型抽象出来,实现解耦。此外还有一个好处,当创建属性动画为kPOPLayerBounds,但toValue属性赋值的是一个NSNumber,得益于_values是数组指针,并不会引发数组越界导致的crash,只是动画效果不可预期。


4、基于NSRunLoop的动画更新机制


-(void)_scheduleProcessPendingList

{

  // see WebKit for magic numbers, eg http://trac.webkit.org/changeset/166540

  staticconstCFIndexCATransactionCommitRunLoopOrder = 2000000;

  staticconstCFIndexPOPAnimationApplyRunLoopOrder = CATransactionCommitRunLoopOrder - 1;

 

  // lock

  OSSpinLockLock(&_lock);

 

  if(!_pendingListObserver){

    __weakPOPAnimator*weakSelf = self;

 

    _pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,kCFRunLoopBeforeWaiting | kCFRunLoopExit,false,POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity){

      [weakSelf_processPendingList];

    });

 

    if(_pendingListObserver){

      CFRunLoopAddObserver(CFRunLoopGetMain(),_pendingListObserver,  kCFRunLoopCommonModes);

    }

  }

 

  // unlock

  OSSpinLockUnlock(&_lock);

}


在主线程RunLoop中添加观察者,监听了kCFAllocatorDefault、kCFRunLoopBeforeWaiting、kCFRunLoopExit事件,在收到回调的时候,处理_pendingList里的动画。


5、更新动画的回调数组


staticPOPStaticAnimatablePropertyState_staticStates[] =

{

  /* CALayer */

 

  {kPOPLayerBackgroundColor,

    ^(CALayer*obj,CGFloatvalues[]){

      POPCGColorGetRGBAComponents(obj.backgroundColor,values);

    },

    ^(CALayer*obj,constCGFloatvalues[]){

      CGColorRefcolor = POPCGColorRGBACreate(values);

      [obj setBackgroundColor:color];

      CGColorRelease(color);

    },

    kPOPThresholdColor

  },

 

  {kPOPLayerBounds,

    ^(CALayer*obj,CGFloatvalues[]){

      values_from_rect(values,[objbounds]);

    },

    ^(CALayer*obj,constCGFloatvalues[]){

      [obj setBounds:values_to_rect(values)];

    },

    kPOPThresholdPoint

  },

...


封装不同的动画行为,实现类似模板模式,只需统一调用,即可更新动画


// write value

write(obj,currentVec->data());


6、动画插值的动态实现


switch(type){

      case kPOPAnimationSpring:

        advanced = advance(time,dt,obj);

        break;

      case kPOPAnimationDecay:

        advanced = advance(time,dt,obj);

        break;

      case kPOPAnimationBasic:{

        advanced = advance(time,dt,obj);

        computedProgress = true;

        break;

      }

      case kPOPAnimationCustom:{

        customFinished = [self _advance:obj currentTime:time elapsedTime:dt]? false : true;

        advanced = true;

        break;

      }

      default:

        break;

    }


可以看出总共有四种动画插值的算法,以kPOPAnimationBasic为例:


booladvance(CFTimeIntervaltime,CFTimeIntervaldt,idobj){

    // default timing function

    if(!timingFunction){

      ((POPBasicAnimation*)self).timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];

    }

 

    // solve for normalized time, aka progresss [0, 1]

    CGFloatp = 1.0f;

    if(duration > 0.0f){

        // cap local time to duration

        CFTimeIntervalt = MIN(time - startTime,duration) / duration;

        p = POPTimingFunctionSolve(timingControlPoints,t,SOLVE_EPS(duration));

        timeProgress = t;

    }else{

        timeProgress = 1.;

    }

 

    // interpolate and advance

    interpolate(valueType,valueCount,fromVec->data(),toVec->data(),currentVec->data(),p);

    progress = p;

    clampCurrentValue();

 

    returntrue;

  }


依照给定的timingFunction,使用POPTimingFunctionSolve计算贝塞尔曲线的变化率,再通过混合计算#define MIX(a, b, f) ((a) + (f) * ((b) - (a))),最终得到动画的插值。


小结


pop中还有很多有意思的地方,譬如TransformationMatrix里的矩阵操作,这里就暂且不挖WebCore底层了。简而言之,无论性能(c++混编)、易用、容错,pop都有着作为引擎该有的特性,而它所暴露的和Core Animation相似的接口也让人极易上手!

0 0
原创粉丝点击