聊聊魔性的动画引擎pop

pop.gif

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

八一八魔性的pop

1、实用的宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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定义了支持的值的数据类型

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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类型的例子:

1
2
3
4
5
6
7
8
9
10
11
12
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的动画更新机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (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、更新动画的回调数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
   },
...

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

1
2
// write value
write(obj, currentVec->data());

6、动画插值的动态实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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相似的接口也让人极易上手!

转载于:https://www.cnblogs.com/linganxiong/p/5626935.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值