zoukankan      html  css  js  c++  java
  • 聊聊魔性的动画引擎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相似的接口也让人极易上手!

  • 相关阅读:
    IIS7.5应用程序池集成模式和经典模式的区别【转】
    Qt keyPressEvent keyReleaseEvent 分析
    cesium编程中级(二)源码编译
    cesium编程中级(一)添加示例到Sandcastle
    cesium编程中级开篇
    cesium编程入门(九)实体 Entity
    cesium编程入门(八)设置材质
    QPushButton 点击信号分析
    cesium编程入门(七)3D Tiles,模型旋转
    cesium编程入门(六)添加 3D Tiles,并调整位置,贴地
  • 原文地址:https://www.cnblogs.com/linganxiong/p/5626935.html
Copyright © 2011-2022 走看看