原文:Controlling Animation Timinggithub
感謝翻譯小組成員@answer-huang(博客)熱心翻譯。本篇文章是咱們每週推薦優秀國外的技術類文章的其中一篇。若是您有不錯的原創或譯文,歡迎提交給咱們,更歡迎其餘朋友加入咱們的翻譯小組(聯繫qq:2408167315)。app
有一種經過CAAnimation實現的協議叫作CAMediaTiming,也就是CABasicAnimation和CAKeyframeAnimation的基類(指CAAnimation)。像duration,beginTime和repeatCount這些時間相關的屬性都在這個類中。大致而言,協議中定義了8個屬性,這些屬性經過一些方式結合在一塊兒,準確的控制着時間。文檔中每一個屬性只有幾句話,因此頗有可能在看這篇文章以前你都已經讀過了,可是我以爲使用可視化的圖形能更好的解釋時間。
可視化的CAMediaTiming
爲了顯示相關屬性的不一樣時間,不管是他們本身仍是混合狀態,我都會動態的將橙色變爲藍色。下面的塊狀顯示了從開始到結束的動畫過程,時間線上每個標誌表明一秒鐘。你能夠看到時間線上的任意一點,當前顏色即表示動畫中的當前時間。好比,duration像下面同樣可視。
duration設置爲1.5秒,因此動畫全程花費了1.5秒變爲藍色。
Figure 1.設置duration爲1.5秒
一旦動畫完成後,CAAnimation默認從layer上移除。這從上面的圖形中也表現出來了。一旦動畫達到最終值,它會從layer上移除。若是layer的顏色是橙色(開始的顏色),那麼顏色又會便會橙色。在這個可視化界面中,layer的顏色是白色,因此你也能夠看到動畫加入layer後的兩秒鐘又會變白,由於那是動畫已經結束了。
咱們也形象的描述一下動畫的beginTime,這樣讓人更容易理解。
Figure 2.設置duration爲1.5秒,開始時間爲1.0秒
durations設置爲1.5秒了,開始時間設爲當前時間。(CACurrentMediaTime())加一秒,因此動畫在2.5s後結束。動畫加入到layer上以後,花費一秒鐘時間來啓動並呈現出來。結果是1+1.5=2.5
爲了讓動畫從fromValue開始顯示,你能夠將動畫設置爲fill backwards。咱們能夠經過設置fillMode爲kCAFillModeBackwards。
Figure 3.Fill mode可讓動畫從fromValue開始顯示
autoreverses屬性能夠產生從初始值到最終值,並反過來回到初始值的動畫。這意味着動畫發生了兩次。
Figure 4.Autoreverses使得動畫結束後又回到起始狀態
和repeatCount比起來,repeatCount能夠將動畫重複兩次(以下所示)或者任意次(你甚至可使用像1.5這樣的分數來完成一個半動畫)。一旦動畫達到它的最終值,他就會立馬跳回到初始值並從新開始
Figure 5.Repeat count可讓動畫運行超過一次
和repeat count相似,但不多用到的就是repeat duration了。它將會根據給定的一個duration簡單的重複動畫(以下2秒所示)。通過一個repeat duration時,若是它小於動畫的duration,那麼動畫就會提早結束(repeat duration以後結束)
Figure 6.Repeat duration會讓動畫根據一個給定duration重複
這些均可以組合起來將一個反轉動畫重複屢次或在一個給定的duration間重複。
Figure 7.組合
一個跟時間相關有趣的屬性是speed。經過設置duration爲3秒,可是speed爲2,動畫快速的執行了1.5秒,由於它的速度是以前的兩倍。
Figure 8.速度爲2時,動畫執行速度是以前的兩倍,因此3秒的動畫只須要執行1.5秒
若是隻是配置了一個簡單的動畫,那麼你也能夠分開使用beginTime和duration以達到相同的效果。可是使用speed屬性的優勢在於這兩個事實:
1.動畫的speed是分等級的。(hierarchical)
2.CAAnimation不是惟一一個實現CAMediaTiming的類。
Hierarchical speed
速度爲2的動畫組有一部分動畫的速度爲1.5,那麼這個動畫就是3倍於正常速度。
CAMediaTiming的其餘實現
CAMediaTiming是CAAnimation實現的一個協議,可是CALayer(全部Core Animationlayers的基類)也實現了相同的協議,這就意味着你能夠設置layer的speed爲2.0,這樣,全部加入到layer的動畫運行都要快兩倍。一樣的,若是一個速度爲3的動畫加到一個速度爲0.5的layer上,這個動畫最終將會以1.5倍的常速運行。
爲了控制動畫或layer的速度,一般還能夠設置speed爲0,從而暫停動畫。和timeOffset結合在一塊兒時,能夠經過像slider相似的控件控制動畫,咱們在下文中也會講到。
剛開始timeOffset屬性是很是奇怪的。正如名字所示那樣,它對時間進行偏移(offset),從而計算出動畫的狀態。以下圖所示。duration爲3秒,offset爲1秒的動畫。
Figure 9.你能夠offset整個動畫,可是動畫全部部分任然會執行
動畫開始運行時跳過第一秒進入從橙色到藍色的過分,直接運行剩下的兩秒,直到徹底變藍。而後動畫直接跳回徹底橙色的時候,並完成第一秒的顏色轉換。這看起來有點像咱們把動畫的第一秒切下來,而後放到最後。
這個屬性不多用,可是它能夠和一個暫停的動畫(speed=0)結合在一塊兒控制’current time’。一個暫停的動畫停留在第一幀。若是你觀察offset動畫每次的第一個顏色,你能夠看到它的顏色值一秒就進入顏色轉換。將time offset設置爲其餘值,你可讓那段時間進入轉換。
控制動畫時間
同時使用speed和timeOffset能夠控制動畫的當前時間。這幾乎不會涉及到什麼代碼,可是概念卻比較難以理解(我但願插圖能有所幫助)。爲了方便,我將動畫的duration設爲1.0。由於time offset是絕對值。這樣作就意味着當time offset爲0.0時,此時就是動畫的0%處(動畫開始),time offset爲1.0時,就是動畫的100%處(動畫結束)。
Slider
以一個簡單的例子開始,咱們爲一個layer的背景色建立一個基本的動畫並增長到layer上。將layer的速度設爲0以暫停動畫。
- CABasicAnimation *changeColor =
- [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
- changeColor.fromValue = (id)[UIColor orangeColor].CGColor;
- changeColor.toValue = (id)[UIColor blueColor].CGColor;
- changeColor.duration = 1.0; // For convenience
- [self.myLayer addAnimation:changeColor
- forKey:@"Change color"];
- self.myLayer.speed = 0.0; // Pause the animation
當拖動slider時,在action方法中將slider的值(將slider的值設爲0-1)設爲layer的time offset
- - (IBAction)sliderChanged:(UISlider *)sender {
- self.myLayer.timeOffset = sender.value; // Update "current time"
- }
下面給出了效果圖:當拖動slider時動畫的值便會改變,而且更新layer的背景色
Figure 10.layer的顏色隨着slider的值改變而改變
拉動刷新
你還可使用另外一種機制來控制動畫時間:像scroll事件。這樣能夠建立一個自定義下拉刷新動畫,當達到加載新數據的臨界值以前,用戶的拖拉操做都會產生一個動畫。在個人這個例子中,scroll事件控制着一個路徑畫筆的動畫。當達到臨界值時,將會啓動另外一種動畫暗示新數據正在加載中。
此次咱們使用scroll view向下拖動的總量來控制時間,爲了標準化,這個值將會以points的形式,這樣是很是好的,由於咱們須要設置一個拖動的臨界值來判斷什麼時候開始加載更多的數據。像下面那樣處理代碼。
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView
- {
- CGFloat offset =
- scrollView.contentOffset.y+scrollView.contentInset.top;
- if (offset <= 0.0 && !self.isLoading) {
- CGFloat startLoadingThreshold = 60.0;
- CGFloat fractionDragged = -offset/startLoadingThreshold;
- self.pullToRefreshShape.timeOffset = MAX(0.0, fractionDragged);
- if (fractionDragged >= 1.0) {
- [self startLoading];
- }
- }
- }
像這樣控制動畫:
- CABasicAnimation *writeText =
- [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
- writeText.fromValue = @0;
- writeText.toValue = @1;
- CABasicAnimation *move =
- [CABasicAnimation animationWithKeyPath:@"position.y"];
- move.byValue = @(-22);
- move.toValue = @0;
- CAAnimationGroup *group = [CAAnimationGroup animation];
- group.duration = 1.0;
- group.animations = @[writeText, move];
結果是:下拉視圖時,能夠直接控制動畫的進度。若是你擡起手動畫將會退回去。
Figure 11.使用scroll事件直接控制拖動刷新
一旦超過臨界值便真正開始加載動畫以及加載更多的數據。我在scrollViewDidScroll:中經過設置isLoading防止timeOffset,開始加載動畫並調整content inset以防止scrollview向上滾動時傳遞loading的指示。
- self.isLoading = YES;
- // start the loading animation
- [self.loadingShape addAnimation:[self loadingAnimation]
- forKey:@"Write that word"];
- CGFloat contentInset = self.collectionView.contentInset.top;
- CGFloat indicatorHeight = CGRectGetHeight(self.loadingIndicator.frame);
- // inset the top to keep the loading indicator on screen
- self.collectionView.contentInset =
- UIEdgeInsetsMake(contentInset+indicatorHeight, 0, 0, 0);
- self.collectionView.scrollEnabled = NO; // no further scrolling
- [self loadMoreDataWithAnimation:^{
- // during the reload animation (where new cells are inserted)
- self.collectionView.contentInset =
- UIEdgeInsetsMake(contentInset, 0, 0, 0);
- self.loadingIndicator.alpha = 0.0;
- } completion:^{
- // reset everything
- [self.loadingShape removeAllAnimations];
- self.loadingIndicator.alpha = 1.0;
- self.collectionView.scrollEnabled = YES;
- self.pullToRefreshShape.timeOffset = 0.0; // back to the start
- self.isLoading = NO;
- }];
最終,當你向下拖動超過臨界值時便會像這樣:
Figure 12.拖動刷新和加載動畫
爲你的應用增長像這樣的動畫能夠很好的豐富應用。而且你能夠像這樣無需大量代碼而作出高級的動畫。這裏我並無展現,可是你能夠經過相似的,或手勢識別或任何其餘直接控制的機制。
示例代碼可在GitHub上下載:
https://github.com/d-ronnqvist/blogpost-codesample-PullToRefresh