转载

聊聊魔性的动画引擎pop

聊聊魔性的动画引擎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 = [value copy]; /   __VA_ARGS__ / }

2、判定值的数据类型

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

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

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

static bool FBCompareTypeEncoding(const char *objctype, POPValueType type) {   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       return strcmp(objctype, @encode(UIEdgeInsets)) == 0; #else       return false; #endif       case kPOPValueAffineTransform:       return strcmp(objctype, @encode(CGAffineTransform)) == 0;       case kPOPValueTransform:       return strcmp(objctype, @encode(CATransform3D)) == 0;       case kPOPValueRange:       return strcmp(objctype, @encode(CFRange)) == 0       || strcmp(objctype, @encode (NSRange)) == 0;       case kPOPValueInteger:       return (strcmp(objctype, @encode(int)) == 0               || strcmp(objctype, @encode(unsigned int)) == 0               || strcmp(objctype, @encode(short)) == 0               || strcmp(objctype, @encode(unsigned short)) == 0               || strcmp(objctype, @encode(long)) == 0               || strcmp(objctype, @encode(unsigned long)) == 0               || strcmp(objctype, @encode(long long)) == 0               || strcmp(objctype, @encode(unsigned long long)) == 0               );       case kPOPValueSCNVector3: #if SCENEKIT_SDK_AVAILABLE       return strcmp(objctype, @encode(SCNVector3)) == 0; #else       return false; #endif       case kPOPValueSCNVector4: #if SCENEKIT_SDK_AVAILABLE       return strcmp(objctype, @encode(SCNVector4)) == 0; #else       return false; #endif       default:       return false;   } }

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

举个CGRect类型的例子:

case kPOPValueRect:       vec = Vector::new_cg_rect([value CGRectValue]);   Vector *Vector::new_cg_rect(const CGRect &r)   {     Vector *v = new Vector(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;     return v;   }

通过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   static const CFIndex CATransactionCommitRunLoopOrder = 2000000;   static const CFIndex POPAnimationApplyRunLoopOrder = CATransactionCommitRunLoopOrder - 1;     // lock   OSSpinLockLock(&_lock);     if (!_pendingListObserver) {     __weak POPAnimator *weakSelf = self;       _pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {       [weakSelf _processPendingList];     });       if (_pendingListObserver) {       CFRunLoopAddObserver(CFRunLoopGetMain(), _pendingListObserver,  kCFRunLoopCommonModes);     }   }     // unlock   OSSpinLockUnlock(&_lock); }

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

5、更新动画的回调数组

static POPStaticAnimatablePropertyState _staticStates[] = {   /* CALayer */     {kPOPLayerBackgroundColor,     ^(CALayer *obj, CGFloat values[]) {       POPCGColorGetRGBAComponents(obj.backgroundColor, values);     },     ^(CALayer *obj, const CGFloat values[]) {       CGColorRef color = POPCGColorRGBACreate(values);       [obj setBackgroundColor:color];       CGColorRelease(color);     },     kPOPThresholdColor   },     {kPOPLayerBounds,     ^(CALayer *obj, CGFloat values[]) {       values_from_rect(values, [obj bounds]);     },     ^(CALayer *obj, const CGFloat values[]) {       [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为例:

bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) {     // default timing function     if (!timingFunction) {       ((POPBasicAnimation *)self).timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];     }       // solve for normalized time, aka progresss [0, 1]     CGFloat p = 1.0f;     if (duration > 0.0f) {         // cap local time to duration         CFTimeInterval t = 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();       return true;   }

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

小结

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

本文作者:伯乐在线 - Hawk0620 

正文到此结束
Loading...