你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 聊聊魔性的動畫引擎pop

聊聊魔性的動畫引擎pop

編輯:IOS開發基礎

pop.gif

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 

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved