動畫中的時間
CAMediaTiming是一個協(xié)議,這個協(xié)議定義了動畫中的時間系統(tǒng),CALayer、CoreAnimation都實(shí)現(xiàn)了這個協(xié)議。CALayer做隱式動畫時候的動畫時間系統(tǒng)也是這個。
從下面兩個角度去了解該協(xié)議中的這個時間系統(tǒng)
這個動畫系統(tǒng)中的時間和系統(tǒng)時間的對應(yīng)關(guān)系
這個協(xié)議中的屬性的作用
動畫系統(tǒng)中的時間和系統(tǒng)時間的對應(yīng)關(guān)系
什么是系統(tǒng)時間
這里說的系統(tǒng)時間是手機(jī)硬件時間,這個時間是從開機(jī)到現(xiàn)在運(yùn)行的時間秒數(shù),可以看做是以手機(jī)開機(jī)為開始的一個時間戳。
系統(tǒng)時間的獲取可以通過CACurrentMediaTime()方法來得到
動畫系統(tǒng)的時間是一套獨(dú)立的時間那么它和系統(tǒng)時間如何對應(yīng)
通過CAMediaTiming協(xié)議中的注釋可以得到以下信息
一個動畫對象中的時間是基于父對象的時間來計算的
-
t = (tp - begin) * speed + timeOffset
t - 本對象的時間 可以是
CALayer對象或者CAAnimation對象tp - 父對象的時間
begin - beginTime
通過這個公式可以推導(dǎo)得到
在默認(rèn)情況下 t = tp , 即 本對象的時間和父對象的時間是一樣的。(begin=0, speed=1, timeOffset=0,默認(rèn)值)
timeOffset = t - tp (當(dāng) speed=1, begin=0)
begin = tp - t (當(dāng) speed=1,timeOffset=0)
用下面的例子來驗證上面的公式
第一組測試,驗證默認(rèn)情況下動畫系統(tǒng)時間與系統(tǒng)時間的關(guān)系
CALayer *testLayer = [CALayer layer];
[self.view.layer addSublayer:testLayer];
CFTimeInterval currentSystemTime = CACurrentMediaTime();
CFTimeInterval layerTime = [testLayer convertTime:currentSystemTime fromLayer:nil];
NSLog(@"systemTime - %lf", currentSystemTime);
NSLog(@"layerTime - %lf", layerTime);
輸出結(jié)果
>>> systemTime - 1726.022083
>>> layerTime - 1726.022083
convertTime方法是將系統(tǒng)時間轉(zhuǎn)換為動畫系統(tǒng)時間(或者叫圖層系統(tǒng)時間)
從上面這個示例可以證明,創(chuàng)建默認(rèn)的layer上的時間與系統(tǒng)時間值是一樣的。所以在默認(rèn)情況下,動畫系統(tǒng)的時間是和系統(tǒng)時間同步的
圖示

創(chuàng)建一個默認(rèn)的layer,因為layer實(shí)現(xiàn)了CAMediaTiming協(xié)議,它的時間會參考父對象,它的父對象也是默認(rèn)值,所以和系統(tǒng)時間是一樣的。所以,它的時間起點(diǎn)和系統(tǒng)時間的起點(diǎn)是一致的。所以獲取的layer的時間和系統(tǒng)時間的值是一樣的。
第二組測試,驗證beginTime對動畫系統(tǒng)時間的影響
CALayer *layer = [CALayer layer];
layer.beginTime = 1;
[self.view.layer addSublayer:layer];
CALayer *layer2 = [CALayer layer];
layer2.beginTime = 1;
[layer addSublayer:layer2];
CFTimeInterval currentSystemTime = CACurrentMediaTime();
CFTimeInterval layerTime = [layer convertTime:currentSystemTime fromLayer:nil];
CFTimeInterval layer2Time = [layer2 convertTime:currentSystemTime fromLayer:nil];
NSLog(@"systemTime - %lf", currentSystemTime);
NSLog(@"layerTime - %lf", layerTime);
NSLog(@"layer2 Time - %lf", layer2Time);
創(chuàng)建一個layer,設(shè)置beginTime為1,添加到self.view.layer
再創(chuàng)建一個layer,設(shè)置beginTime為1,添加到上面的layer中
輸出結(jié)果
>>> systemTime - 36914.257435
>>> layerTime - 36913.257435
>>> layer2 Time - 36912.257435
上面結(jié)果可以看到,layer的本地時間比系統(tǒng)時間慢1s,而layer2的本地時間比系統(tǒng)時間慢2s
原因如下圖

layer設(shè)置beginTime為1,然后添加self.view.layer中,而self.view.layer是與系統(tǒng)時間一致的,所以當(dāng)beginTime=1的時候,layer的時間就會在self.view.layer的時間為1s的時候才啟動,或者說在self.view.layer執(zhí)行到1秒的時候,才會將這個layer添加。所以得到的結(jié)果為layer的時間比系統(tǒng)時間慢1s
而對于layer2,它是添加到layer上的,所以它的時間是相對于layer來計算的,當(dāng)對layer2設(shè)置beginTime=1的時候,它會在layer1執(zhí)行到1s的時候,啟動它的時間計算或者說這個時候才被添加到layer1上面,而本來layer的時間是比系統(tǒng)時間慢1s,所以layer2相比于系統(tǒng)時間就是慢了2s
結(jié)論
從上面兩個例子可以理解什么是CAMediaTiming協(xié)議中的有層級的時間系統(tǒng),因為所有創(chuàng)建的layer對象或者CAAnimation對象,他們都有獨(dú)立的時間系統(tǒng),但是是基于父對象的時間計算。
在默認(rèn)的情況下,該對象的時間是與系統(tǒng)時間一致對應(yīng)的。
但是當(dāng)屬性值更改后,它會出現(xiàn)不對應(yīng)的情況,即動畫對象的本地時間的第10s是系統(tǒng)時間的第20s。換算公式上面已經(jīng)給出。
CAMediTiming協(xié)議中屬性對時間的影響
beginTime
上面的例子已經(jīng)可以看到,beginTimn這個屬性影響的是這個時間對象的開始計時時間,即在父對象的什么時間開始啟動計時。
這個值是一個時間戳,也就是說,beginTime=13,就是在父對象的第13秒的時候開始啟動。
可以理解為beginTime設(shè)置的是一個時間點(diǎn),在這個時間點(diǎn)開始自己的計時系統(tǒng)
所以一般使用beginTime的時候,都是使用CACurrentTime()+x來設(shè)置該屬性,原因是,對于大多數(shù)的情況,父對象的本地時間和系統(tǒng)時間是一致的,所以當(dāng)設(shè)置延遲啟動的時候直接獲取系統(tǒng)當(dāng)前時間,然后加x秒則就是延遲x秒后啟動。
比如像CAAnimation對象,因為它一旦加入到layer上就開始了動畫,所以它的beginTime影響的就是動畫的開始時間。
下面的示例來展示beginTime的影響
- (void) showBeginTimeEffect {
UIView *showView = [UIView new];
showView.frame = CGRectMake(50, 50, 50, 50);
showView.backgroundColor = [UIColor greenColor];
[self.view addSubview:showView];
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position";
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 50)];
animation.duration = 2;
animation.beginTime = CACurrentMediaTime() + 2;
[showView.layer addAnimation:animation forKey:nil];
}
創(chuàng)建一個動畫對象CABasicAnimation,如果不設(shè)置beginTime,默認(rèn)為0,則添加到layer上立即進(jìn)行動畫。
而設(shè)置beginTime=CACurrentMediaTime() + 2則是將動畫在2s之后添加到layer上去執(zhí)行動畫。
原理如下圖所示

timeOffset
一個動畫對象在動畫期間,每一時刻對應(yīng)一個動畫狀態(tài)
這個屬性的作用是將動畫的狀態(tài)設(shè)置為該動畫中指定時刻的狀態(tài)
注意設(shè)置的是一個該對象的本地時間,
它是相對于本地時間0s的一個偏移。
下面兩個示例來看如何設(shè)置本地時間
- (void) showTimeOffsetEffect {
UIView *showView = [UIView new];
showView.frame = CGRectMake(50, 50, 50, 50);
showView.backgroundColor = [UIColor greenColor];
[self.view addSubview:showView];
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position";
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 50)];
animation.duration = 4;
animation.beginTime = CACurrentMediaTime() + 2;
animation.timeOffset = 2;
[showView.layer addAnimation:animation forKey:nil];
}
beginTime將動畫在2秒之后開始,也就是在2秒之后,該動畫對象的本地時間才開始計時,即本地時間0s開始,設(shè)置timeOffset=2,表示動畫的開始狀態(tài)直接設(shè)置為動畫對象第2秒時候的狀態(tài)。所以動畫一開始這個視圖就直接移動到中間位置,然后開始繼續(xù)移動。
通過動畫暫停的示例來看timeOffset
- (void) showTimeOffSetEffect {
UIView *fastView = [UIView new];
fastView.backgroundColor = [UIColor greenColor];
fastView.frame = CGRectMake(50, 100, 50, 50);
[self.view addSubview:fastView];
[UIView animateWithDuration:5.0 animations:^{
fastView.frame = CGRectMake(300, 100, 50, 50);
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CFTimeInterval pauseTime = CACurrentMediaTime();
NSLog(@"系統(tǒng)時間 == %lf", pauseTime);
CFTimeInterval time = [fastView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
NSLog(@"本地時間 == %lf", time);
fastView.layer.speed = 0;
fastView.layer.timeOffset = time;
});
}
上面這個示例是將動畫暫停動畫執(zhí)行2秒時的狀態(tài) convertTime方法是將系統(tǒng)時間轉(zhuǎn)換為本地時間
這里的fastView.layer.timeOffset不能直接設(shè)置為2,不然就會將動畫的狀態(tài)停在了本地時間第2秒的時候
而我們想要的是停止在動畫執(zhí)行2s的狀態(tài),所以要獲取動畫執(zhí)行2s時是本地時間的多少秒
如圖所示

區(qū)別
上面兩個屬性的總結(jié)
beginTime設(shè)置的是一個時間點(diǎn),這個時間點(diǎn)是父對象的時間點(diǎn)。表示在父對象的什么時間點(diǎn)開始子對象的計時系統(tǒng)
timeOffset設(shè)置的也是一個時間點(diǎn),但是這個時間點(diǎn)是該對象的本地時間的時間點(diǎn)。表示將動畫狀態(tài)設(shè)置為該動畫指定時間時狀態(tài)。
這兩個屬性的關(guān)鍵在于時間的參考系不一樣