美文网首页
FaceBook Pop 源码分析 (2)

FaceBook Pop 源码分析 (2)

作者: 充满活力的早晨 | 来源:发表于2018-09-17 20:46 被阅读69次

上篇分析到- (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item 函数,只是罗列了下里面的基本逻辑。没有仔细分析,想看懂这里面的函数要弄到POPAnimationState 结构体以及子结构体
这里我们先分析结构体并且依次分析结构体里面的函数

_POPAnimationState 结构体的结构

_POPAnimationState.png

好多属性和标志位

初始化变量的值

 _POPAnimationState(id __unsafe_unretained anim) :
  self(anim),
  type((POPAnimationType)0),
  name(nil),
  ID(0),
  beginTime(0),
  startTime(0),
  lastTime(0),
  delegate(nil),
  completionBlock(nil),
  dict(nil),
  tracer(nil),
  progress(0),
  repeatCount(0),
  active(false),
  paused(true),
  removedOnCompletion(true),
  delegateDidStart(false),
  delegateDidStop(false),
  delegateDidProgress(false),
  delegateDidApply(false),
  delegateDidReachToValue(false),
  additive(false),
  didReachToValue(false),
  tracing(false),
  userSpecifiedDynamics(false),
  autoreverses(false),
  repeatForever(false),
  customFinished(false) {}

初始化注意的点是

type=0 ,0 =kPOPAnimationSpring。所以默认的是kPOPAnimationSpring类型
paused = true ,默认是暂停状态
removedOnCompletion =true 默认是当动画完成要移除的
其他的参数都是默认值,0

  bool isCustom() {
    return kPOPAnimationCustom == type;
  }
type 是不是 kPOPAnimationCustom
  bool isStarted() {
    return 0 != startTime;
  }
动画是否开启,这里是根据startTime。看这里需要对比beginTime。有啥区别
  id getDelegate() {
    return delegate;
  }
获取代理
  void setDelegate(id d) {
    if (d != delegate) {
      delegate = d;
      delegateDidStart = [d respondsToSelector:@selector(pop_animationDidStart:)];
      delegateDidStop = [d respondsToSelector:@selector(pop_animationDidStop:finished:)];
      delegateDidProgress = [d respondsToSelector:@selector(pop_animation:didReachProgress:)];
      delegateDidApply = [d respondsToSelector:@selector(pop_animationDidApply:)];
      delegateDidReachToValue = [d respondsToSelector:@selector(pop_animationDidReachToValue:)];
    }
  }
设置代理的同时,检查这个对象是否实现了代理的相关方法,这样不用重复调用respondsToSelector进行对方法检测
  bool getPaused() {
    return paused;
  }
  获取动画是否暂停
  void setPaused(bool f) {
    if (f != paused) {
      paused = f;
      if (!paused) {
        reset(false);
      }
    }
  }
设置动画是否暂停,调用 reset 方法,只是重设startTime和lastTime
  virtual void reset(bool all) {
    startTime = 0;
    lastTime = 0;
  }

 CGFloat getProgress() {
    return progress;
  }
获取动画进度

以上还是都是简单的setter 和getter 方法。不做详细介绍

下面的是动画执行实际逻辑顺序

 /* returns true if started */
  bool startIfNeeded(id obj, CFTimeInterval time, CFTimeInterval offset)
  {
    bool started = false;
    
    // detect start based on time
    if (0 == startTime && time >= beginTime + offset) {
      
      // activate & unpause
      active = true;
      setPaused(false);
      
      // note start time
      startTime = lastTime = time;
      started = true;
    }

    // ensure values for running animation
    bool running = active && !paused;
    if (running) {
      willRun(started, obj);
    }

    // handle start
    if (started) {
      handleDidStart();
    }

    return started;
  }

这个函数用来标记是否开启动画

1 首次调用,我们将active 设置true ,paused=false,代表动画激活并且当前需要执行动画
2 如果是激活并且没有暂停,那么调用 willRun方法(willRun是需方法,需要子类去实现)
3 要是 动画启动,调用handleDidStart
4 返回started ,true代表已经启动

  virtual void handleDidStart()
  {
    if (delegateDidStart) {
      ActionEnabler enabler;
      [delegate pop_animationDidStart:self];
    }
    
    if (tracing) {
      [tracer didStart];
    }
  }

这个函数逻辑

1 如果delegate 实现了pop_animationDidStart 方法,那么delegate调用该方法
2 如果开始了tracing 。tracer 调用didStart方法. tract是POPAnimationTracer 类,这个不是重点,暂时过。

void stop(bool removing, bool done) {
    if (active)
    {
      // delegate progress one last time
      if (done) {
        delegateProgress();
      }
      
      if (removing) {
        active = false;
      }
      
      handleDidStop(done);
    } else {
      
      // stopped before even started
      // delegate start and stop regardless; matches CA behavior
      if (!isStarted()) {
        handleDidStart();
        handleDidStop(false);
      }
    }
    
    setPaused(true);
  }

这个函数是暂停动画的

1 要是当前是active 激活状态,那么调用2,未激活,那么调用5
2 要是传入的参数done是yes,那么调用虚函数delegateProgress
3 要是传入的参数removing是YEs,将active 状态变更没NO
4调用handleDidStop 函数。这个函数,调用了completionBlock
5 设置动画标志是pause = true

ActionEnabler enabler;

C++ 写法,打开隐式动画。

  /* virtual functions */
  virtual bool isDone() {
    if (isCustom()) {
      return customFinished;
    }
    
    return false;
  }

动画是否结束。看样子kPOPAnimationCustom类型的动画不太一样。

  bool advanceTime(CFTimeInterval time, id obj) {
    bool advanced = false;
    bool computedProgress = false;
    CFTimeInterval dt = time - lastTime;

    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;
    }
    
    if (advanced) {
      
      // estimate progress
      if (!computedProgress) {
        computeProgress();
      }
      
      // delegate progress
      delegateProgress();
      
      // update time
      lastTime = time;
    }
    
    return advanced;
  }

这算是动画执行的具体分配了

1 计算开始动画到现在的时间
2 根据type 调用响应的advance 方法 .这里看出来有四种动画
3 根据advance 获取结果,进行响应的调整
4 将time 赋值给lastTime变量

这里调用 virtual bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { return false; } 函数
我们看看这个函数的参数

1 time 当前时间
2 dt 是动画间隔时间
3 obj 是动画对象
因为是虚函数,需要子类继承才能起作用。这里有四个子类。struct _POPDecayAnimationStatestruct _POPCustomAnimationState,struct _POPSpringAnimationState,struct _POPBasicAnimationState

我们看看这个类的几个虚函数都是在哪实现的。并且都干嘛了

virtual void willRun(bool started, id obj) {} 全局搜索只在struct _POPPropertyAnimationState 显示
virtual bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { return false; } 实现的地方说过了
virtual void computeProgress() {}struct _POPPropertyAnimationState 实现
virtual void delegateProgress() {} 在struct _POPPropertyAnimationState 实现

这个结构体实现的原理大概是这样的


startIfNeeded.png

willRun

上图中我们没有分析willRun ,这里我们就看看这个函数吧。实现在
struct _POPPropertyAnimationState 。这个函数很关键,在每次刷新都会调用willrun,这里面应该包含一些做动画的一些准备工作

 virtual void willRun(bool started, id obj) {
    // ensure from value initialized
    if (NULL == fromVec) {
      readObjectValue(&fromVec, obj);
    }

    // ensure to value initialized
    if (NULL == toVec) {
      // compute decay to value
      if (kPOPAnimationDecay == type) {
        [self toValue];
      } else {
        // read to value
        readObjectValue(&toVec, obj);
      }
    }

    // handle one time value initialization on start
    if (started) {

      // initialize current vec
      if (!currentVec) {
        currentVec = VectorRef(Vector::new_vector(valueCount, NULL));

        // initialize current value with from value
        // only do this on initial creation to avoid overwriting current value
        // on paused animation continuation
        if (currentVec && fromVec) {
          *currentVec = *fromVec;
        }
      }

      // ensure velocity values
      if (!velocityVec) {
        velocityVec = VectorRef(Vector::new_vector(valueCount, NULL));
      }
      if (!originalVelocityVec) {
        originalVelocityVec = VectorRef(Vector::new_vector(valueCount, NULL));
      }
    }

    // ensure distance value initialized
    // depends on current value set on one time start
    if (NULL == distanceVec) {

      // not yet started animations may not have current value
      VectorRef fromVec2 = NULL != currentVec ? currentVec : fromVec;

      if (fromVec2 && toVec) {
        Vector4r distance = toVec->vector4r();
        distance -= fromVec2->vector4r();

        if (0 != distance.squaredNorm()) {
          distanceVec = VectorRef(Vector::new_vector(valueCount, distance));
        }
      }
    }
  }

按步骤分析

1 保证fromVec 有值
2 保证ToVec 有值, 因此动画么。总要有开始结束
3 这里要是第一次调用willRun 执行4 ,不是第一次调用执行6
4.第一次执行,检查currentVec,currentVec ,那么分配valueCount个值给currentVec,并且让currentVec = fromVec;
5 分别判断velocityVec 和originalVelocityVec ,没空间,分配空间
6 要是distanceVec == NULL, 那么找到fromVec 和ToVec 间隔
7 将间隔保存在distanceVec 中

这里我们设置了distanceVec 和 currentVec

bool advance(CFTimeInterval time, CFTimeInterval dt, id obj)

接下来我们看看具体的动画计算struct _POPBasicAnimationState

 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;
  }

1.检查是否有时间函数。
2 看看是否有动画执行时间,没有直接进度到1,否则进行3
3 根据当前时间计算出动画进度(这里涉及到贝塞尔曲线)
4 根据当前进度计算出当前layer 状态
5 调用clampCurrentValue 函数(暂时不知道啥结果)

这里我们需要看看POPTimingFunctionSolve 函数的实现

double POPTimingFunctionSolve(const double vec[4], double t, double eps)
{
  WebCore::UnitBezier bezier(vec[0], vec[1], vec[2], vec[3]);
  return bezier.solve(t, eps);
}

这里有个结构体 struct UnitBezier ,有六个成员变量

    double ax;
    double bx;
    double cx;
    
    double ay;
    double by;
    double cy;

他们其实是三阶贝塞尔曲线的系数。这里暂时就简单推到下,有时间专门开篇文章讲贝塞尔曲线

三阶贝塞尔曲线的公式
三阶贝塞尔曲线公式

公式展开
B(t)=P0 - 3P0t + 3P0t2 - P0t3 + 3P1t - 2*3P1t 2 + 3P1t3+ 3P2t2 - 3P2t3 + P3t3

多项式合并
B(t) = (- P0 + 3P1 - 3P2 + P3)t3 + (3P0 - 2*3P1 + 3P2)t2 + ( - 3P0 + 3P1)t + P0
整理
B(t) = [P3 - P0 -3(P2 - P1)]t3 + [3(P2 - P1) + 3(P0 - P1)]t2 + 3(P1 - P0)t + P0

这里我们已经知道贝塞尔的四个点坐标
P0 = (0,0)
P1 = (px1,py1)
P2 = (px2,py2)
P3 = (1,1)
带入上述公式结果是 计算x轴
B(t) = [1-0-3(px2-px1)]t3 +[3(px2-px1)+3(0-px1)]t2 + 3(px1-0)t +0
计算整理获取
B(t) = [1-3(px2-px1)]t3 +[3(px2-px1)-3px1]t2 + 3px1t

这里我们用cx = 3p3x1;
替代获取
B(t) = [1-3(px2-px1)]t3 +[3(px2-px1)-cx]t2 + cxt

我们用bx = 3(px2-px1)-cx; 那么 bx+cx = 3(px2-px1)
结果 B(t) = [1-3(px2-px1)]t3 +bxt2 + cxt

我们用ax = 1-3(px2-px1)= 1-(bx+cx) = 1-bx-cx
最终替代结果是
B(t) = axt3 +bxt2 + cxt
ax = 1-bx-cx;
bx = 3(px2-px1)-cx;
cx = 3p3x1;

计算公式推导完毕

这里还有个函数很重要,我看了老半天,不知道理解的对不对,我把我自己的见解写出来

    // Given an x value, find a parametric value it came from.
    double solveCurveX(double x, double epsilon)
    {
      double t0;
      double t1;
      double t2;
      double x2;
      double d2;
      int i;
      
      // First try a few iterations of Newton's method -- normally very fast.
        ///
      for (t2 = x, i = 0; i < 8; i++) {
          ///获取t2 时间x 周的坐标
        x2 = sampleCurveX(t2) - x;
          NSLog(@"%lf %lf  %lf",t2 , x ,x2);
        if (fabs (x2) < epsilon)
          return t2;
          NSLog(@"====%lf",x2);
          ///计算斜率
        d2 = sampleCurveDerivativeX(t2);
          NSLog(@"d2====%lf",d2);

        if (fabs(d2) < 1e-6)
          break;
          ///
        t2 = t2 - x2 / d2;
      }
       NSLog(@"error");
      // Fall back to the bisection method for reliability.
      t0 = 0.0;
      t1 = 1.0;
      t2 = x;
      
      if (t2 < t0)
        return t0;
      if (t2 > t1)
        return t1;
      
      while (t0 < t1) {
        x2 = sampleCurveX(t2);
        if (fabs(x2 - x) < epsilon)
          return t2;
        if (x > x2)
          t0 = t2;
        else
          t1 = t2;
        t2 = (t1 - t0) * .5 + t0;
      }
      
      // Failure.
      return t2;
    }

double sampleCurveX(double t) 获取x轴的坐标
double sampleCurveDerivativeX(double t) 获取x轴在的导数
x2 = sampleCurveX(t2) - x; 获取 间距

image.png
x2 间距不能太太大了。太大要缩小两者之间的距离
太大,我们就需要再x 和t2 中间找,最多执行八次这样的查找。
要是没找到,那么就用二分法找,再没找到就结束了

因此double solve(double x, double epsilon) 方法就是找到x时间的百分比

看步骤四
tatic void interpolate(POPValueType valueType, NSUInteger count, const CGFloat *fromVec, const CGFloat *toVec, CGFloat *outVec, CGFloat p)
{
  switch (valueType) {
    case kPOPValueInteger:
    case kPOPValueFloat:
    case kPOPValuePoint:
    case kPOPValueSize:
    case kPOPValueRect:
    case kPOPValueEdgeInsets:
    case kPOPValueColor:
      POPInterpolateVector(count, outVec, fromVec, toVec, p);
      break;
    default:
      NSCAssert(false, @"unhandled type %d", valueType);
      break;
  }
}
给dst 赋值,即**currentVec->data()**赋值
void POPInterpolateVector(NSUInteger count, CGFloat *dst, const CGFloat *from, const CGFloat *to, CGFloat f)
{
  for (NSUInteger idx = 0; idx < count; idx++) {
    dst[idx] = MIX(from[idx], to[idx], f);
  }
}

#define MIX(a, b, f) ((a) + (f) * ((b) - (a)))

这里别把mix 看着max。 **mix 计算在f时间后的偏移位置,并且将数据保存在crrent->data()中

上面这个函数只是计算animation 下一帧动画layer所在的位置。具体是如何设置的呢?

applyAnimationTime

这个函数在 - (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item中调用,- (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item函数我们知道是跟着CADisplayLink的回调同步调用。

applyAnimationTime,包含动画计算下一帧位置和设置动画位置

static void applyAnimationTime(id obj, POPAnimationState *state, CFTimeInterval time)
{
  if (!state->advanceTime(time, obj)) {
    return;
  }
  
  POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state);
  if (NULL != ps) {
    updateAnimatable(obj, ps);
  }
  state->delegateApply();
}

1 我们知道advanceTime,是根据时间计算出current->data的数据。
2 获取propertyAimationState 调用** updateAnimatable** 更新object的位置
3 自定义动画调用

接下来看如何更新layer 的位置的

static void updateAnimatable(id obj, POPPropertyAnimationState *anim, bool shouldAvoidExtraneousWrite = false)
{
 // handle user-initiated stop or pause; hault animation
 if (!anim->active || anim->paused)
   return;

 if (anim->hasValue()) {
   pop_animatable_write_block write = anim->property.writeBlock;
   if (NULL == write)
     return;

   // current animation value
   VectorRef currentVec = anim->currentValue();

   if (!anim->additive) {

     // if avoiding extraneous writes and we have a read block defined
     if (shouldAvoidExtraneousWrite) {

       pop_animatable_read_block read = anim->property.readBlock;
       if (read) {
         // compare current animation value with object value
         Vector4r currentValue = currentVec->vector4r();
         Vector4r objectValue = read_values(read, obj, anim->valueCount);
         if (objectValue == currentValue) {
           return;
         }
       }
     }
     
     // update previous values; support animation convergence
     anim->previous2Vec = anim->previousVec;
      
     anim->previousVec = currentVec;
//        NSLog(@"%lf",currentVec->data()[0]);
     // write value
     write(obj, currentVec->data());
     if (anim->tracing) {
       [anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)];
     }
   } else {
     pop_animatable_read_block read = anim->property.readBlock;
     NSCAssert(read, @"additive requires an animatable property readBlock");
     if (NULL == read) {
       return;
     }

     // object value
     Vector4r objectValue = read_values(read, obj, anim->valueCount);

     // current value
     Vector4r currentValue = currentVec->vector4r();
     
     // determine animation change
     if (anim->previousVec) {
       Vector4r previousValue = anim->previousVec->vector4r();
       currentValue -= previousValue;
     }

     // avoid writing no change
     if (shouldAvoidExtraneousWrite && currentValue == Vector4r::Zero()) {
       return;
     }
     
     // add to object value
     currentValue += objectValue;
     
     // update previous values; support animation convergence
     anim->previous2Vec = anim->previousVec;
     anim->previousVec = currentVec;
     
     // write value
     write(obj, currentValue.data());
     if (anim->tracing) {
       [anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)];
     }
   }
 }
}

1 判断anim的状态,要是标志位没有激活或者暂停状态,就不进行绘制了
2 判断anim是否含有值,没有也就结束了。(这里是通过valueCount 值判断的,因为我们没设置一个动画这个值就会累加,没减少一个动画,valueCount也响应的减少)
3 获取anim 的property的writeBlock。没有就返回了
4 获取当前current->data
5判断!anim->additive 的状态,是1 那么执行6,否则执行8
6 判断shouldAvoidExtraneousWrite 是否是yes,动画没执行完毕之前shouldAvoidExtraneousWrite=false(这里是避免重复绘制动态操作)
7 调用writeBlock.。结束
8 获取下readBlock, 是nil 就返回了
9 累加动画 (这里需要看上下文,暂时过了,basicAnimation不执行这里)
10执行writeBlock .结束

动画结束

  • (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item

中有这部分代码

      if (state->isDone()){
        // set end value
        applyAnimationToValue(obj, state);

        state->repeatCount--;
        if (state->repeatForever || state->repeatCount > 0) {
          if ([anim isKindOfClass:[POPPropertyAnimation class]]) {
            POPPropertyAnimation *propAnim = (POPPropertyAnimation *)anim;
            id oldFromValue = propAnim.fromValue;
            propAnim.fromValue = propAnim.toValue;

            if (state->autoreverses) {
              if (state->tracing) {
                [state->tracer autoreversed];
              }

              if (state->type == kPOPAnimationDecay) {
                POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim;
                decayAnimation.velocity = [decayAnimation reversedVelocity];
              } else {
                propAnim.toValue = oldFromValue;
              }
            } else {
              if (state->type == kPOPAnimationDecay) {
                POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim;
                id originalVelocity = decayAnimation.originalVelocity;
                decayAnimation.velocity = originalVelocity;
              } else {
                propAnim.fromValue = oldFromValue;
              }
            }
          }

          state->stop(NO, NO);
          state->reset(true);

          state->startIfNeeded(obj, time, _slowMotionAccumulator);
        } else {
          stopAndCleanup(self, item, state->removedOnCompletion, YES);
        }
      }

1 *static void applyAnimationToValue(id obj, POPAnimationState state) 调用,就是将current->data 更新到结束的地方
2 将重复次数减少一
3 检查是否所有动画执行结束了。如果需要动画重复,那么就行4 否则 就执行8

  1. 判断动画是否需要倒转 是执行5 不是执行6
  2. 倒转将 tovalue 和fromValue 倒置
    6 不倒置不用理
    7 设置动画标志位不变,复位动画,调用startIfNeeded 继续开始动画
    8 调用stopAndCleanup 停止动画
static void stopAndCleanup(POPAnimator *self, POPAnimatorItemRef item, bool shouldRemove, bool finished)
{
  // remove
  if (shouldRemove) {
    deleteDictEntry(self, item->unretainedObject, item->key);
  }

  // stop
  POPAnimationState *state = POPAnimationGetState(item->animation);
  state->stop(shouldRemove, finished);

  if (shouldRemove) {
    // lock
    OSSpinLockLock(&self->_lock);

    // find item in list
    // may have already been removed on animationDidStop:
    POPAnimatorItemListIterator find_iter = find(self->_list.begin(), self->_list.end(), item);
    BOOL found = find_iter != self->_list.end();

    if (found) {
      self->_list.erase(find_iter);
    }

    // unlock
    OSSpinLockUnlock(&self->_lock);
  }
}

1 判断动画执行完毕是否需要移除
2 停止动画.
3 要是执行完毕移除动画,删除动画。

大概流程讲解完毕了。


popAnimationExe.png

这里没有分析POPSpringAnimation 动画是因为不会弹簧算法。

这里我们就简单举例说明用法就行了POPCustomAnimation 动画

- (void)performAnimation
{
    self.tapGesture.enabled = NO;
    
    // First let's remove any existing animations
    CALayer *layer = self.label.layer;
    [layer pop_removeAllAnimations];
    
    POPCustomAnimation * anim = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) {
        static int m  =0;
        m++;
        [(CALayer *)target setBackgroundColor:UIColor.redColor.CGColor];
        [(CALayer *)target setFrame:CGRectMake(0, 50+m*2, 100, 50)];
        
        if (m>100) {

            m=0;
            return NO;
        }
        NSLog(@"%@",target);
        return YES;
    }];
    
    anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
        NSLog(@"Animation has completed.");
        self.tapGesture.enabled = YES;
    };
    
    [layer pop_addAnimation:anim forKey:@"size"];
}

相关文章

网友评论

      本文标题:FaceBook Pop 源码分析 (2)

      本文链接:https://www.haomeiwen.com/subject/bmuggftx.html