iOS 動畫篇 - UIKit動畫(二)
簡單使用篇
簡介
iOS10帶來了很多新特性,其中有個 UIViewPropertyAnimator
類,光從名字上就可以看出,這是一個操作屬性動畫的類。實際上,這個類能夠讓我們對檢視進行動畫控制,我們除了可進行正常的執行動畫,如開始、暫停、重啟等操作動畫,還可以將動畫轉換為互動式動畫,任意的控制時間。
它可以對檢視的可動畫屬性進行操作,例如frame,center,alpha 和 transform等,並且可以任意的新增多個動畫塊和完成塊,相比於之前的 UIView 動畫,它改變了我們習慣的動畫流程,變得更加靈活。
簡單例子
改變一個檢視的 center 動畫:
// 建立動畫器 UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 curve:UIViewAnimationCurveEaseOut animations:^{ self.contentView.center = self.view.center; }]; // 開始動畫 [animator startAnimation];

移動位置的動畫
在使用 UIViewPropertyAnimator 做動畫時,需要關注下面幾個點:
- 包含改變一個或多個檢視屬性的動畫塊
- 用於定義動畫執行過程中的時間速率曲線
- 動畫的持續時間(以秒為單位)
- 動畫完成塊(可選)
在上面的簡單例項中,我們在1秒的時間內,改變了檢視的中心位置,其中動畫塊即為 animations 的程式碼塊,在此程式碼塊中我們可以針對可動動規劃進行新增改變。對於執行的動畫時間速率,動畫器 animator 支援 UIKit 動畫中的時間速率函式,即linear、ease-in、ease-out等。
一般來說,我們所建立的動畫器都是處於非活躍狀態,需要手動呼叫 -startAnimation
將其變為活躍狀態執行動畫。
初始化動畫器
UIViewPropertyAnimator 為我們提供了多個快捷建立動畫器的方法。
- 使用內建
時間速率函式
-initWithDuration:curve:animations:
這種方式就是我們節例子中的使用到的建立方法,curve 引數即時間速率函式,其所支援的以下幾種:
UIViewAnimationCurveEaseInOut //緩進緩出 UIViewAnimationCurveEaseIn //緩進 UIViewAnimationCurveEaseOut //緩出 UIViewAnimationCurveLinear //線性勻速

四種內建時間速率
如果所說 UIKit 提供的速率曲線函式不能夠滿足你的執行動畫的速率要求,你還可以通過自定義來建立自己的速度曲線。
- 使用
三次貝塞爾曲線
-initWithDuration:controlPoint1:controlPoint2:animations:
三次貝塞爾曲線的起點為(0,0)且其終點為(1,1),因此兩個控制點的取值範圍是(0,1)。
- 使用
基於彈簧的彈性
-initWithDuration:dampingRatio:animations:
dampingRatio:
所對應的引數叫做阻尼,一般去值為(0,1)較低的阻尼值對應較小阻力和在靜止之前更多更大的振盪。反之則阻力大,振盪少而小。例如你想不振盪的情況下平滑的減速動畫,就可以指定值為1。
// 建立動畫器 UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 dampingRatio:0.35 animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); }]; // 開始動畫 [animator startAnimation];

彈性動畫
- 使用
自定義時間速率物件
-initWithDuration:timingParameters:
該方法需要你提供支援 UITimingCurveProvider
協議的物件,如果你要自定義實現此協議,必須提供所有屬性的實現。
系統有兩個遵循該協議的類
UICubicTimingParameters UISpringTimingParameters
如果你檢視 UICubicTimingParameters
類時,你會發現,這個類也只是提供了支援 UIKit 內建的時間速率曲線和三次貝塞爾曲線。類似的 UISpringTimingParameters
也提供了 CASpringAnimation
中的幾個物理引數。
示例:我們通過該方法實現一下和上一個方法類似的效果
// 彈性的時間速率 UISpringTimingParameters* parameters = [[UISpringTimingParameters alloc] initWithDampingRatio:0.35]; // 建立動畫器 UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 timingParameters:parameters]; // 由於該建立方法沒有動畫塊,因此需要自行追加 [animator addAnimations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); }]; // 開始動畫 [animator startAnimation];

彈性的時間速率
如果說,你受不了每次都需要主動呼叫 -startAnimation
方法來啟動檢視動畫,還是習慣 UIView 的快捷使用!:ok_hand:,蘋果似乎注意到了這一點,為了適應開發者的習慣,除了上述幾種建立動畫器的方式,還有一種可以啟動開啟動畫並能返回當前動畫器的方法。
- 類方法便捷
+ runningPropertyAnimatorWithDuration:delay:options:animations:completion:
該方法提供了動畫的幾個相對比較重要的引數,如動畫執行時間、延遲時間、時間速率、動畫塊、完成塊。該方法相容了 UIView 動畫塊的形式。
[UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationCurveEaseOut animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:^(UIViewAnimatingPosition finalPosition) {}];

快捷使用
控制動畫
UIViewPropertyAnimator 遵守了 UIViewImplicitlyAnimating 協議,而UIViewImplicitlyAnimating 協議是 UIViewAnimating 協議的子類,該類定義瞭如何控制動畫的協議。除了上一節中使用到的 -startAnimation
方法,還有其他幾個控制動畫的方法。
- 開始執行動畫
-startAnimation
:方法可以啟動動畫或者在暫停動畫後恢復動畫。
-startAnimationAfterDelay:
:和上面方法類似,不過可以指定延遲執行的時間
- 暫停動畫
-pauseAnimation
:暫停動畫,當使用該方法後,動畫會停留在“當前位置”,會保持當前的狀態。暫停後可以使用 -startAnimation
恢復,恢復的動畫會從“當前位置”繼續剩餘的動畫,包括剩餘的時間。
示例:我們執行一個2秒時長的動畫,在1秒處停止,延遲1秒後恢復動畫,讓其繼續執行。
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:nil]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 暫停當前動畫 [animator pauseAnimation]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 恢復動畫 [animator startAnimation]; }); });

動畫的暫停和恢復
-
-stopAnimation:
:停止動畫
停止動畫有多種情況,由於動畫狀態的機制(進階篇會講)的存在,當我們停止動畫後,這些動畫狀態資訊何去何從?蘋果給出了兩種的去處,一種時清除所有狀態資訊,動畫器重置為初始的非活躍狀態,以等待下一個動畫;另外一種是保留所有狀態資訊,等待下一步操作。這裡的 withoutFinishing 引數就是用來指明去處。
引數 withoutFinishing,表示是否應執行任何最終操作。如果值為 YES,則會清除任何動畫並將動畫器重置為非活躍狀態,並且不會執行完成塊的回撥。
示例:我們執行一個2秒的動畫,在一秒處停止當前動畫,並且在完成塊中將檢視的背景色更改為紅色。
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:^(UIViewAnimatingPosition finalPosition) { // 動畫的完成回撥 self.contentView.backgroundColor = UIColor.redColor; }]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [animator stopAnimation:YES]; });

withoutFinishing為YES的情況
執行結果發現,動畫在1秒處停止了,但是並沒變成紅色背景,這說明,此時的動畫器並不會執行完成塊。
當引數為 NO 時,動畫器狀態為 stopped,此時通常會配合 finishAnimationAtPosition:
使用,該方法可以幫助動畫器執行最終的完成塊的內容,當然,這兩個方法的目的是停下當前動畫,讓你完成此刻需要完成的內容,如其他動畫,之後,你再使用 finishAnimationAtPosition:
完成動畫的回撥以及動畫需要停止的位置。
在演示示例之前,我們來介紹一下 finishAnimationAtPosition:
。
-
-finishAnimationAtPosition:
:結束動畫
該方法可以將處於 stopped 狀態的動畫重置為非活躍狀態,並執行動畫的完成塊。
此方法通常配合 -stopAnimation:
使用,並且該方法必須在動畫器狀態為 stopped 狀態才可以,否則會出現錯誤。該方法的 UIViewAnimatingPosition
引數有一下三種:
UIViewAnimatingPositionEnd //動畫的終點位置 UIViewAnimatingPositionStart //動畫的開頭位置 UIViewAnimatingPositionCurrent //動畫當前位置
指定 UIViewAnimatingPositionCurrent 以使檢視屬性與其當前值保持不變。
示例:我們繼續之前的例子,這次我們配合 -finishAnimationAtPosition:
方法使用。
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:^(UIViewAnimatingPosition finalPosition) { // 動畫的完成回撥 self.contentView.backgroundColor = UIColor.redColor; }]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [animator stopAnimation:NO]; // 動畫器的狀態必須是stopped if (animator.state==UIViewAnimatingStateStopped) { [animator finishAnimationAtPosition:UIViewAnimatingPositionCurrent]; } });

withoutFinishing為NO的情況
我們發現,動畫執行1秒後停止了,並且背景色被填充為紅色,這說明 -finishAnimationAtPosition:
觸發完成塊,這一點和之前的例子是不同的。另外,我們看到檢視停下來之後就保持在了當前位置,這是因為我們給的結束位置就是 Current。下圖演示了位置的不同引數的效果。

start、current、end三種不同位置
互動式動畫
fractionComplete 屬性
UIViewPropertyAnimator 類中有一個 fractionComplete
屬性,這個屬性表示當前動畫的完成的百分比,並且這個屬性不是隻讀的屬性,這說明我們可以精準的控制動畫的整個過程。利用它,我們可以製作互動式動畫。互動式動畫的好處是:對於多個檢視、非常複雜的檢視變化加以控制變得簡單。
示例:
@interface ViewController () @property (weak, nonatomic) IBOutlet UIView *blueView; @property (weak, nonatomic) IBOutlet UIView *redView; @property (weak, nonatomic) IBOutlet UISlider *slider; @property (strong, nonatomic) UIViewPropertyAnimator* animator; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 初始化動畫器 self.animator = [[UIViewPropertyAnimator alloc] initWithDuration:2.0 curve:UIViewAnimationCurveLinear animations:^{ // 紅色檢視 CGRect fram = CGRectMake(self.slider.center.x - 50/2.0, self.slider.center.y - 100, 50, 50); self.redView.frame = fram; self.redView.transform = CGAffineTransformMakeRotation(M_PI); self.redView.backgroundColor = UIColor.blueColor; // 藍色檢視 self.blueView.frame = fram; self.blueView.transform = self.redView.transform; self.blueView.backgroundColor = UIColor.redColor; }]; [self.slider addTarget:self action:@selector(change:) forControlEvents:UIControlEventValueChanged]; } -(void)change:(UISlider*)slider{ CGFloat value = slider.value; // 更改動畫完成度 self.animator.fractionComplete = value; }

控制兩個檢視之間的動畫
需要注意的是,在使用 fractionComplete
之前,最好呼叫 -pauseAnimation
暫停當前動畫,此時動畫處於活躍狀態,但非 isRunning
。
修改動畫
正如前面簡介中提到過,UIViewPropertyAnimator 可以修改動畫,甚至是在動畫處於執行狀態。我們可以新增多個動畫塊、完成塊,設定是暫停掉正在執行的動畫,並且修改它的剩餘時間,這讓我們更加的精準的控制檢視的動畫行為。
-
-addAnimations:
:為檢視新增動畫塊
我們之前在使用 自定義時間速率物件
初始化動畫器時,曾經使用到過該方法,此方法可以讓我們對檢視的動畫追加多個動畫塊。
示例:我們為正在運動的檢視新增漸變動畫
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:nil]; // 追加動畫塊 [animator addAnimations:^{ self.contentView.backgroundColor = UIColor.redColor; }];

追加動畫塊
我們追加的動畫塊會和其他動畫共享動畫器剩餘的時間。
示例:延遲追加動畫
為了明顯的看出效果,我們給予更長的動畫時間,並在執行一段時間後,追加動畫。
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:4.0 delay:0 options:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:nil]; // 追加動畫塊 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [animator addAnimations:^{ self.contentView.backgroundColor = UIColor.redColor; }]; });

共享時間
我們看到2秒後追加的漸變動畫在剩餘的2秒內完成了漸變效果。
當然,蘋果已經給出了類似的方法,無需我們主動寫延遲方法。就像下面的演示。
-
-addAnimations:delayFactor:
:延遲追加動畫塊
引數 delayFactor
是指時間因子,即動畫的進度,取值區間為(0,1)。比如,0.5表示動畫執行一半的時候執行。
示例:我們使用該方法完成之前的例子
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:4.0 delay:0 options:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); } completion:nil]; // 延遲追加動畫塊 [animator addAnimations:^{ self.contentView.backgroundColor = UIColor.redColor; } delayFactor:0.5];

延遲載入動畫
-
-addCompletion:
:追加完成塊
既然動畫塊都可以追加修改,那麼完成塊也應該相應的有追加方法呀!
在初始化以動畫器一節中,我們發現大部分都是不帶有完成塊回撥的,蘋果似乎考慮到開發過程中很少會關心動畫的完成事件吧,因此為了方法的簡潔性,就讓其變成了可選特性,又或者這樣設計會讓動畫變得更加的靈活,因為這樣,動畫的完成事件就無需緊跟在初始化方法上了。
示例:我們在動畫執行完成時執行一些事情
這裡為了方便看到效果,我們就直接來改變檢視的顏色。
UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 curve:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); }]; [animator startAnimation]; // 追加完成塊 [animator addCompletion:^(UIViewAnimatingPosition finalPosition) { self.contentView.backgroundColor = UIColor.redColor; }];

追加完成塊
在【控制動畫】一節中,我們提到過,我們可以呼叫 -startAnimation
:來恢復暫停後的動畫,但是這樣做的話,動畫的形式依舊是之前設定好的情況,它並不會發生變化。那麼,如果想要暫定動畫後,執行其他時間速率的動畫該怎麼辦呢?別急,既然說了 UIViewPropertyAnimator 可以讓我們任意的控制動畫,必然會提供該類方法。
-
-continueAnimationWithTimingParameters:durationFactor:
:暫停後修改動畫方式繼續執行
該類方法只會在呼叫 -pauseAnimation
方法之後起到作用,此時的動畫狀態為,活躍但非 isRunning
。
引數 durationFactor
是時間因子,表示動畫的進度。通常可以取 fractionComplete
屬性。
示例:我們將勻速執行中的檢視中途改為彈性運動
UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:2 curve:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); }]; [animator startAnimation]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 暫停動畫之後 [animator pauseAnimation]; // 暫停動畫之後,修改動畫時間速率後繼續動畫 UISpringTimingParameters* timing = [[UISpringTimingParameters alloc] initWithDampingRatio:0.2]; [animator continueAnimationWithTimingParameters:timing durationFactor:animator.fractionComplete]; });

中途更改動畫
上圖演示了中途更改的動畫情況,其中,底下的檢視為參考檢視,即未改變的勻速運動。
以上就是快速入門使用的所有教程了,相信經過一系列的介紹之後,你能夠快速的使用新的動畫方式了。
接下來的進階篇,會講解一些 UIViewPropertyAnimator 的一些細節部分。
進階篇
動畫協議
其實要介紹 UIViewPropertyAnimator 類前,應該先介紹其遵循的動畫協議--UIViewAnimating 和 UIViewImplicitlyAnimating 。前者是後者的父類,我們先來解釋 UIViewAnimating 協議。該協議定義了操作動畫的基本方法,包括啟動、停止、暫停動畫的能力。另外還有幾個屬性用於反映動畫的當前狀態資訊。
UIViewPropertyAnimator 遵循並實現了 UIViewAnimating 協議的所有方法,因此我們可以用 UIViewPropertyAnimator 來實現動畫的控制。如果你想在自定義類中也遵循此協議,最好實現所有的協議方法和屬性。
動畫的狀態
UIViewPropertyAnimator 有著一套完整的狀態機制。在動畫器處理一組動畫時,都會伴隨著這一系列的動畫狀態。這些狀態定義了動畫器的行為,包括它是如何處理變化的。如果你在實現自定義動畫器,必須遵循這些狀態的轉換並準確的更新狀態屬性。下圖顯示了發生的狀態和狀態轉換關係。

狀態轉換關係
Inactive
非活躍狀態是動畫器的初始狀態。每個新建立的動畫器都會處於非活躍狀態下啟動。相對的,動畫正常完成後會返回到非活躍狀態。
當我們呼叫 -startAnimation
或 -pauseAnimation
方法時,此時動畫器會變為 Active
活躍狀態。此狀態下的動畫器正在執行或暫停狀態。如果是動畫被暫停,我們此時還可以修改動畫時間速率曲線,讓後讓其繼續執行到預期結束,結束後的動畫器狀態依舊是 Inactive
非活躍狀態,等待我們使用一組新的動畫重新配置它,以便開始新的動畫。
當我們開啟動畫之後,呼叫 -stopAnimation:
方法會停止正在執行的動畫,此時檢視會被保留在停止的那一刻的值。此方法的引數值決定了當前的動畫資訊是否被擦除。如果引數 withoutFinishing 是 YES,則表示擦除當前動畫的資訊,動畫器進入 Inactive
狀態,需要注意,這種情況下,動畫器是不會執行完成塊的,換句話說你無法在完成塊中得到動畫結束資訊,此時如果你需要知道動畫結束的事件,你可以使用 KVO 的方法監聽屬性 isRunning
獲得。如果引數 withoutFinishing 是 NO,則表示保留當前動畫的資訊,動畫器進入 Stopped
狀態,此時我們可以去完成其他的操作,如執行其他動畫。然後我們呼叫方法 -finishAnimationAtPosition:
以結束此次動畫,動畫器順理成章的進入到 Inactive
狀態。注意,這情情況下,動畫器可以順利的執行完成塊內容。
動畫狀態的幾個列舉:
typedef NS_ENUM(NSInteger, UIViewAnimatingState) { UIViewAnimatingStateInactive, // The animation is not executing. UIViewAnimatingStateActive,// The animation is executing. UIViewAnimatingStateStopped,// The animation has been stopped and has not transitioned to inactive. } NS_ENUM_AVAILABLE_IOS(10_0) ;
協議內容
方法
-
-startAnimation
開始動畫
不可以在動畫器呼叫方法 -stopAnimation:
直接結束動畫後再次呼叫 -startAnimation
,換句話說,使用過程中,出現下面情況會出錯:

錯誤

錯誤
我們發現,在我們 -stopAnimation:
指定引數為 YES時,動畫器狀態由活躍狀態 Active
轉變為非活躍狀態 Inactive
,此時再次呼叫 -startAnimation
時,系統丟擲了異常。
而我們指定引數為 NO時,

正常
此時動畫器狀態為 stopped
,程式並未出錯。

正常
這一點和官方文件的說明並不一致,目前還不是很清楚原因。
It is a programmer error to call this method while the state of the animator is set to UIViewAnimatingStateStopped.
:warning::11-29,以上結論基於10.3.2系統,但是筆者使用11以上的系統發現,結論和上述相反,卻和官方文件一致,即動畫器狀態為 stopped
下,不能使用 -startAnimation
。這一點讓我更加凌亂了,難道後面的系統修正了?
-
-startAnimationAfterDelay:
延遲後開始動畫
上面的開啟動畫一樣的注意點同樣適用。(請注意上面 11-29 的說明)
另外經測試發現, -pauseAnimation
之後呼叫 -startAnimationAfterDelay:
會發生程式錯誤。
-
-pauseAnimation
暫停動畫
暫停動畫後,可以使用 -startAnimation
方法重新恢復動畫,另外你也可以使用協議 UIViewImplicitlyAnimating
中的 continueAnimationWithTimingParameters:durationFactor:
恢復動畫。如果動畫已經暫停,則再次呼叫 -pauseAnimation
不會執行任何操作。
經測試發現如果動畫器從未啟動過,直接呼叫 -pauseAnimation
方法,如果緊接著呼叫 -startAnimation
或者 continueAnimationWithTimingParameters:durationFactor:
是無法恢復動畫的,之間需要大於千分之一秒的時間,就像下面的情況:
無法恢復動畫的情況:
[self.animator pauseAnimation]; [self.animator startAnimation]; //[self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5];
如果之前呼叫過開啟動畫,則可以恢復動畫
[self.animator startAnimation]; ... [self.animator pauseAnimation]; [self.animator startAnimation]; //[self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5];
又或者新增延遲
// 從未開啟過,暫停動畫 [self.animator pauseAnimation]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.002 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.animator startAnimation]; //[self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5]; });
至於為什麼會時這種情況,官方文件並未指出,或許只有蘋果自己清楚吧。
另外,官方文件指出動畫器為 stopped
狀態時,呼叫 -pauseAnimation
會出現程式錯誤,但並沒有。
:warning::11-29,以上結論基於10.3.2系統,使用11以上的系統發現並不會出現【無法啟動暫停動畫】的情況,並且,動畫器為 stopped
狀態時,呼叫 -pauseAnimation
時,確實出現程式錯誤。
-
-stopAnimation:
停止動畫
需要注意的是,不可以在動畫器狀態由 Active
轉為為 stopped
的時候再呼叫該方法。下面的使用會發生程式錯誤:

錯誤使用
上圖中的呼叫 -pauseAnimation
將動畫器轉為 Active
也會出現錯誤。
這一點,官方文件卻並未提及。why?
-
-finishAnimationAtPosition:
結束動畫
該方法通常會和上面的停止方法相結合,用來結束當前停止下來(狀態為 stopped
)動畫順利回到 Inactive
狀態。經測試,該方法只認 stopped
狀態,其他兩個狀態都會發生錯誤。
屬性
-
fractionComplete
動畫執行的進度
描述了當前動畫的進度,可被更改,當動畫處於停止時,可配合手勢等實現互動式動畫。
-
reversed
是否可以反轉動畫
關於反轉動畫,目前還未知如果實現反轉動畫。
-
state
動畫器的狀態 -
running
動畫的執行狀態,支援KVO
修改動畫協議
UIViewImplicitlyAnimating 繼承自 UIViewAnimating 協議,在後者協議的基礎上又添加了一些額外的修改動畫的方法。而我們使用的 UIViewPropertyAnimator 動畫器就遵循了這個相對完善的協議,並實現了所有的方法。
方法
-
-addAnimations:
新增動畫塊
使用此方法可以將新的動畫塊新增到自定義動畫物件。新的動畫會與先前的動畫一起執行,並從當前時間開始並與任何原始動畫同時結束。
-
-addAnimations:delayFactor:
新增延遲動畫塊
同上,不過會從指定的延遲開始並與任何原始動畫同時結束。
引數 delayFactor:用於延遲動畫開始的時間因子。該值必須介於0.0和1.0之間。將此值乘以動畫剩餘持續時間,作為實際延遲。例如,如果值0.5、動畫器的持續時間為2.0,則延遲一秒執行動畫。
-
-addCompletion:
新增動畫完成塊
回撥動畫完成的事件,你可以在該 block 中完成其他操作。
引數 withoutFinishing 有三種,表示最後動畫結束的位置。
typedef NS_ENUM(NSInteger, UIViewAnimatingPosition) { UIViewAnimatingPositionEnd, UIViewAnimatingPositionStart, UIViewAnimatingPositionCurrent, } NS_ENUM_AVAILABLE_IOS(10_0);
如果動畫正常完成結束,位置引數為 UIViewAnimatingPositionEnd
,即最終的期望位置;
如果動畫執行過程中,呼叫了 -stopAnimation:
,並且制定的引數為 YES,動畫器則不會呼叫完成塊;
如果動畫執行過程中,呼叫了 -stopAnimation:
,並且制定的引數為 NO,此時需要呼叫 finishAnimationAtPosition:
配置結束動畫,此時完成塊中的位置引數由方法 finishAnimationAtPosition:
決定。
-
-continueAnimationWithTimingParameters:durationFactor:
調整暫停的動畫的時間速率曲線和持續時間
引數 parameters 是指時間速率曲線,系統提供了兩種,相容了 UIKit 內建的四種時間速率曲線、三次貝塞爾曲線、彈簧式的彈性動畫。
UICubicTimingParameters UISpringTimingParameters
引數 durationFactor 是指動畫原始持續時間的因子,取值為(0,1),將此值乘以動畫的原始持續時間,作為新的持續時間。
UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:2 curve:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); }]; [animator startAnimation]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [animator pauseAnimation]; [animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:animator.fractionComplete]; });

修改動畫持續時間
如果我們將 durationFactor 傳遞為當前動畫器的進度值 fractionComplete ,你會發現執行的動畫並沒有什麼變化,這是因為 fractionComplete 的值乘以原始持續時間就等於動畫剩餘的時間。
但是我們將值放大,比 fractionComplete 的值要大,那麼動畫的剩餘時間就會被拉長,剩下的動畫會在新的時間內完成。
示例:我們將動畫1秒後,將剩餘的時間縮短為0.1倍
UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:2 curve:UIViewAnimationCurveLinear animations:^{ self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y); }]; [animator startAnimation]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [animator pauseAnimation]; [animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.1]; });

修改剩餘動畫的持續時間
當前就像之前介紹的,你也可以改換當前動畫的時間速率曲線,或者更換為彈性動畫。
動畫器
介紹完 UIViewPropertyAnimator 的兩種協議之後,我們來看下 UIViewPropertyAnimator 中的一些其小細節。
三次貝塞爾曲線
在構建動畫器的方法中,有一個之前我們一提而過的方法:
-initWithDuration:controlPoint1:controlPoint2:animations:
這個方法可以讓我們自定義時間速率曲線,採用的是三次貝塞爾曲線,可能有些人並不清楚什麼是三次貝塞爾曲線。三次貝塞爾曲線在繪製圖形時經常出現,是有起點、終點以及兩個控制點產生的曲線。文字描述比較抽象,我們來看下圖:

三次貝塞爾曲線
該曲線的起點為(0,0),其終點為(1,1)。point1 和 point2 引數是定義生成的貝塞爾曲線形狀的控制點。其中,起點和控制點1的連線為曲線的切線,終點和控制點2的連線也是曲線的切線,控制點1和控制點2的連寫也是曲線的切線,這樣,產生的曲線就是三次貝塞爾曲線。
該曲線的斜率定義了動畫的不同時間速率,斜率越大,速度越快,斜率越小速度越慢。上圖顯示了一個速率曲線,其中動畫快速啟動並快速完成,但在中間部分執行得相對較慢。
屬性
-
duration
只讀
動畫的持續時間,只有在初始化動畫器時指定該值,稍後新增的動畫僅在剩餘的時間內執行。剩餘時間由公式(1.0 - fractionComplete)* 持續時間確定。
-
delay
只讀
延遲動畫時間,預設值為0。如果要為此屬性設定值,啟動動畫時則需要使用 startAnimationAfterDelay:
方法
-
timingParameters
只讀
描述速度的曲線,和duration一樣,只有在初始化 animator 指定該值。可以使用此屬性稍後獲取這些引數。
-
interruptible
動畫中是否可被打斷。當此屬性的值為 YES 時,我們可以使用 -pauseAnimation
和 -stopAnimation:
方法來中斷動畫並進行更改。當此屬性的值為 NO 時,在呼叫startAnimation方法後,動畫將執行至完成(並且不會中斷)。
如果使用動畫器來實現可中斷的檢視控制器轉換,則此屬性必須為 YES。
-
userInteractionEnabled
動畫中使用者是否可互動。預設值為 YES。當此屬性的值為 YES 時,觸控事件將正常傳遞給檢視,否則在動畫持續時間內會忽略使用者的觸控事件。
-
manualHitTestingEnabled
動畫中點選測試的能力。預設為 NO。
-
scrubsLinearly
暫停的動畫是否使用線性擦除或者使用指定的時間速率曲線。iOS11之後可用。
-
pausesOnCompletion
動畫完成後是否保持活動狀態。預設值為 NO。iOS11之後可用。
當此屬性的值為 YES 時,動畫器完成後動畫後將保持 Active
狀態,並且不會執行完成塊。此時我們可以撤消動畫。當此屬性的值為 NO 時,動畫完成後,動畫器執行完成塊,自動轉換為 Inactive
狀態,從而結束動畫。
注:由於 YES 的情況下,動畫器並且不會執行完成塊,因此如果你想要知道動畫的結束事件,你需要監聽動畫器的 running
屬性。
補充
- 設定同一可動畫屬性
-addAnimations:
方法可以讓我們新增多個屬性動畫塊,那麼,如果兩個或多個動畫需要同時改變相同的屬性會發生什麼呢?蘋果採用的是“後者優先”原則。即:後新增的動畫效果會覆蓋之前的動畫效果。但有趣的是,這將導致卡頓,因為需要組合新舊動畫,在舊動畫淡出的同時會隱約看見新動畫。
示例:
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.contentView.transform = CGAffineTransformMakeScale(1.5, 1.5); [animator addAnimations:^{ self.contentView.transform = CGAffineTransformMakeScale(0.5, 0.5); }];

設定同一動畫屬性
我們發現,後新增的縮小為0.5的動畫效果覆蓋了之前的放大為1.5的動畫效果。但這似乎看不出所為卡頓的效果,那我們來看下填充背景色會發生什麼。
UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.contentView.backgroundColor = UIColor.redColor; } completion:nil]; [animator addAnimations:^{ self.contentView.backgroundColor = UIColor.yellowColor; }];

設定同一動畫屬性
該示例中,檢視的最初顏色為藍色,在第一個動畫塊中,我們將其設定為紅色,後又設定為黃色。在該動畫執行過程中,我們發現,檢視立刻被設定為紅色,然後由紅色漸變為黃色。因此,在多個動畫塊中設定同一個動畫屬性並不可控,我們應該儘可能的避免這種情況的出現。
總結
UIViewPropertyAnimator 類讓我們能夠精準的控制檢視動畫的每個細節。我們可以使用該類的示例完成各種動畫的設定,中途修改動畫,甚至可以便捷的完成互動性的動畫,這徹底改變了我們設定檢視動畫的習慣,這些改變令人驚喜萬分。
在進階篇中,我們發現了很多異常的情況,並且在不同的系統上有著不同的表現,甚至是完全相反的情況,這一點讓人非常的疑惑,筆者猜想可能是蘋果在 iOS11 系統之後改變了 UIViewPropertyAnimator 的一些實現細節部分,導致了前後不一致的情況,但是在這種情況下,想要使用該類需要異常謹慎。
但是,如果不能夠因噎廢食,如果我們開發過程中能夠注意到這些異常的情況,避免這些異常操作,UIViewPropertyAnimator 不失為一個較為良好的動畫類。
根據之前的問題,有幾點建議:
-
建立完動畫器之後,請使用
-startAnimation
和-startAnimationAfterDelay:
方法開啟動畫,或者直接使用+ runningPropertyAnimatorWithDuration:delay:options:animations:completion:
-
在
stopped
情況下,請配合-finishAnimationAtPosition:
方法結束後續動畫,而非其他方法 -
在暫停動畫的情況下,請使用
-startAnimation
和-continueAnimationWithTimingParameters:durationFactor:
方法恢復動畫,可能的話,請保證動畫是由執行中暫停的,或者延遲大於千分之秒的時間恢復動畫