動畫的時間系統(tǒng) - beginTime timeOffset屬性

動畫中的時間

CAMediaTiming是一個協(xié)議,這個協(xié)議定義了動畫中的時間系統(tǒng),CALayer、CoreAnimation都實(shí)現(xiàn)了這個協(xié)議。CALayer做隱式動畫時候的動畫時間系統(tǒng)也是這個。

從下面兩個角度去了解該協(xié)議中的這個時間系統(tǒng)

  1. 這個動畫系統(tǒng)中的時間和系統(tǒng)時間的對應(yīng)關(guān)系

  2. 這個協(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)得到

  1. 在默認(rèn)情況下 t = tp , 即 本對象的時間和父對象的時間是一樣的。(begin=0, speed=1, timeOffset=0,默認(rèn)值)

  2. timeOffset = t - tp (當(dāng) speed=1, begin=0)

  3. 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)時間同步的

圖示

default_layer_time

創(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

原因如下圖

begin_time_effect

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í)行動畫。

原理如下圖所示

beginTimeEffect

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時是本地時間的多少秒

如圖所示

timeOffset

區(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)鍵在于時間的參考系不一樣

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容