聊聊魔性的动画引擎pop
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相似的接口也让人极易上手!