在解析pop源碼以前,咱們先經過面向過程的方式來實現系統動畫,來理解動畫的本質。由於若是從面向對象的方式直接去解析,很容易就繞到對象與對象之間的關聯中。oop
假設咱們須要將一個view向下移動600px,那麼可使用UIView Animation來實現post
- (void)startSystemAnimation {
[UIView animateWithDuration:1.0 delay:0.f options:UIViewAnimationOptionCurveEaseIn animations:^{
self.animatedView.layer.transform = CATransform3DMakeTranslation(0, 300, 0);
} completion:nil];
}
複製代碼
對於這個簡單的系統動畫,咱們須要分別瞭解如下內容:動畫
簡單來講,視圖的動畫就是在必定時間內,動畫的屬性(位置、顏色或大小)發生線性或非線性的變化。假設咱們要實現上面的系統動畫,那麼咱們要明確時間、變化效果和變化對象三者。ui
CADisplayLink是一個可讓咱們以和屏幕刷新率相同的頻率將內容渲染到屏幕上的定時器。與NSTimer最大的區別就是時間偏差小且調用時機與屏幕刷新率相同,可以保證動畫的流暢性。spa
該屬性定義了一系列的動畫效果,具體能夠參考UIViewAnimationOptions,此次咱們主要探討動畫速度控制的效果。code
UIViewAnimationOptionCurveEaseInOut:動畫先加速、後減速[默認]。
UIViewAnimationOptionCurveEaseIn :動畫開始後逐漸加速。
UIViewAnimationOptionCurveEaseOut:動畫快結束時進行減速。
UIViewAnimationOptionCurveLinear :動畫勻速執行,默認值。
複製代碼
該類主要定義了動畫的緩衝效果,上面UIViewAnimationOptions的四種動畫效果其實是經過CAMediaTimingFunction來實現的,具體以下:orm
kCAMediaTimingFunctionLinear//勻速的線性計時函數
kCAMediaTimingFunctionEaseIn//緩慢加速,而後忽然中止
kCAMediaTimingFunctionEaseOut//全速開始,慢慢減速
kCAMediaTimingFunctionEaseInEaseOut//慢慢加速再慢慢減速
kCAMediaTimingFunctionDefault//也是慢慢加速再慢慢減速,可是它加速減速速度略慢
複製代碼
在使用系統動畫時,咱們經過CATransform3DMakeTranslation方法來修改視圖的位置,而CATransform3D自己是一個矩陣,它矩陣中對應的屬性以下:
struct CATransform3D{
CGFloat m11(x縮放), m12(y切變), m13(旋轉), m14();
CGFloat m21(x切變), m22(y縮放), m23, m24;
CGFloat m31(旋轉), m32, m33, m34(透視效果,要有旋轉角度才能看出效果);
CGFloat m41(x平移), m42(y平移), m43(z平移), m44;
};
複製代碼
如上所示,矩陣中對應的m42即爲視圖y軸方向上的平移,經過修改該屬性咱們就能夠達到修改視圖y軸方向的位置了。
t0: 開始時間
t1: 當前時間
t2: 結束時間
t1 = currentTime();
t2 = t0 + duration;
while (t1 < t2) {
offset = caculateOffset(); //計算偏移量
obj.offset = offset;
t1 = currentTime(); //更新當前時間
}
複製代碼
- (void)startCustomAnimation {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
static bool isStarted = NO;
static CFTimeInterval beginTime = 0;
static CFTimeInterval endTime = 0;
- (void)render:(CADisplayLink *)displayLink { //正常狀況下,每秒調用60次,即每16ms調用一次
CGFloat translationY = 300;
CFTimeInterval duration = 1.0;
if (!isStarted) { //記錄動畫開始時間和結束時間
beginTime = CACurrentMediaTime();
endTime = CACurrentMediaTime() + duration;
isStarted = YES;
}
CFTimeInterval curTime = CACurrentMediaTime();
if (curTime < endTime) {
CFTimeInterval interval = curTime - beginTime;
CGFloat offset = [self linearWithInterval:interval duration:duration translation:translationY]; //獲取當前時間對應的位置
CATransform3D transform = CATransform3DIdentity;
transform.m42 = offset; //更新視圖對應的位置
self.animatedView.layer.transform = transform;
} else {
displayLink.paused = YES; //動畫結束,中止計時器
[displayLink invalidate];
}
}
//線性計算視圖對應的偏移:勻速變化
- (CGFloat)linearWithInterval:(CFTimeInterval)interval duration:(CFTimeInterval)duration translation:(CGFloat)transation {
return interval / duration * transation;
}
複製代碼
CAMediaTimingFunction緩衝動畫的本質是使用三次貝塞爾曲線,經過曲線來描述動畫的變化速率,具體貝塞爾曲線相關內容可參考: 貝塞爾曲線
求解三次貝塞爾曲線方程
B(t) = (3*P1-3*P2+1)*t^3 + (3*P2-6*P1)*t^2 + 3*P1*t
咱們須要根據x(時間點)計算出f(x)偏移量,那麼須要先解出係數t來,係數t的求解通常採用牛頓法或二分法.下面先採用牛頓法進行求解,若沒有獲得合適的解,再經過二分法來求解。
- (CGFloat)curveEaseInWithInterval:(CFTimeInterval)interval duration:(CFTimeInterval)duration translation:(CGFloat)transation {
CAMediaTimingFunction *timingFunc = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
float timingControls[4];
[timingFunc getControlPointAtIndex:1 values:&timingControls[0]];
[timingFunc getControlPointAtIndex:2 values:&timingControls[2]];
CGPoint controlPoint1 = CGPointMake(timingControls[0], timingControls[1]);
CGPoint controlPoint2 = CGPointMake(timingControls[2], timingControls[3]); //獲取控制點位置
CGFloat p1x = controlPoint1.x, p1y = controlPoint1.y;
CGFloat p2x = controlPoint2.x, p2y = controlPoint2.y;
CGFloat ax,bx,cx,ay,by,cy; //分別表示t,t^2和t^3前的係數
cx = 3.0 * p1x;
bx = 3.0 * (p2x - p1x) - cx;
ax = 1.0 - cx - bx;
cy = 3.0 * p1y;
by = 3.0 * (p2y - p1y) - cy;
ay = 1.0 - cy - by;
CGFloat (^sampleCurveX)(CGFloat t) = ^CGFloat (CGFloat t) { //座標x中的三次貝塞爾曲線公式
return ((ax * t + bx) * t + cx) * t; //秦九韶算法
};
CGFloat (^sampleCurveY)(CGFloat t) = ^CGFloat (CGFloat t) { //座標y中的三次貝塞爾曲線公式
return ((ay * t + by) * t + cy) * t;
};
CGFloat (^sampleCurveDerivativeX)(CGFloat t) = ^CGFloat (CGFloat t) { //求導函數
return (3 * ax * t + 2.0 * bx) * t + cx;
};
CGFloat (^solveCurveX)(CGFloat x, CGFloat epsilon) = ^(CGFloat x, CGFloat epsilon) { //求解公式中的因子t
CGFloat t0,t1,t2,x2,d2;
int i;
//牛頓法求解
for (t2 = x, i = 0; i < 8; i ++) {
x2 = sampleCurveX(t2) - x;
if (fabs(x2) < epsilon) {
return t2;
}
d2 = sampleCurveDerivativeX(t2);
if (fabs(d2) < 1e-6) {
break;
}
t2 = t2 - x2 / d2;
}
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) * 0.5 + t0;
}
return t2;
};
CGFloat epsilon = 1.0 / (1000 * duration); //偏差容許範圍
CGFloat t = solveCurveX(interval / duration, epsilon); //計算出係數t
return sampleCurveY(t) * transation; //根據公式計算出時間點對應的偏移量,而後再乘以總偏移量
}
複製代碼
本文主要使用一種面向過程的方式來實現簡單的動畫,但其實它裏面已經包括了pop中的一些關鍵代碼,只是pop是使用面向對象的方式來實現動畫,以保證動畫更靈活、更高效,關於pop的具體實現方式,後面會逐一介紹。