上篇分析到- (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 _POPDecayAnimationState,struct _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轴的坐标
image.png
double sampleCurveDerivativeX(double t) 获取x轴在的导数
x2 = sampleCurveX(t2) - x; 获取 间距
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
- 判断动画是否需要倒转 是执行5 不是执行6
- 倒转将 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"];
}
网友评论