iOS 動畫篇 - UIKit動畫(二)

簡單使用篇

簡介

iOS10帶來了很多新特性,其中有個 UIViewPropertyAnimator 類,光從名字上就可以看出,這是一個操作屬性動畫的類。實際上,這個類能夠讓我們對視圖進(jìn)行動畫控制,我們除了可進(jìn)行正常的運行動畫,如開始、暫停、重啟等操作動畫,還可以將動畫轉(zhuǎn)換為交互式動畫,任意的控制時間。

它可以對視圖的可動畫屬性進(jìn)行操作,例如frame,center,alpha 和 transform等,并且可以任意的添加多個動畫塊和完成塊,相比于之前的 UIView 動畫,它改變了我們習(xí)慣的動畫流程,變得更加靈活。

簡單例子

改變一個視圖的 center 動畫:

// 創(chuàng)建動畫器
UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0
                                                                              curve:UIViewAnimationCurveEaseOut
                                                                         animations:^{
    self.contentView.center = self.view.center;
}];
// 開始動畫
[animator startAnimation];
移動位置的動畫

在使用 UIViewPropertyAnimator 做動畫時,需要關(guān)注下面幾個點:

  • 包含改變一個或多個視圖屬性的動畫塊
  • 用于定義動畫運行過程中的時間速率曲線
  • 動畫的持續(xù)時間(以秒為單位)
  • 動畫完成塊(可選)

在上面的簡單實例中,我們在1秒的時間內(nèi),改變了視圖的中心位置,其中動畫塊即為 animations 的代碼塊,在此代碼塊中我們可以針對可動動規(guī)劃進(jìn)行新增改變。對于運行的動畫時間速率,動畫器 animator 支持 UIKit 動畫中的時間速率函數(shù),即linear、ease-in、ease-out等。

一般來說,我們所創(chuàng)建的動畫器都是處于非活躍狀態(tài),需要手動調(diào)用-startAnimation將其變?yōu)榛钴S狀態(tài)執(zhí)行動畫。

初始化動畫器

UIViewPropertyAnimator 為我們提供了多個快捷創(chuàng)建動畫器的方法。

  • 使用內(nèi)置時間速率函數(shù)

-initWithDuration:curve:animations:

這種方式就是我們節(jié)例子中的使用到的創(chuàng)建方法,curve 參數(shù)即時間速率函數(shù),其所支持的以下幾種:

UIViewAnimationCurveEaseInOut //緩進(jìn)緩出
UIViewAnimationCurveEaseIn //緩進(jìn)
UIViewAnimationCurveEaseOut //緩出
UIViewAnimationCurveLinear //線性勻速
四種內(nèi)置時間速率

如果所說 UIKit 提供的速率曲線函數(shù)不能夠滿足你的執(zhí)行動畫的速率要求,你還可以通過自定義來創(chuàng)建自己的速度曲線。

  • 使用三次貝塞爾曲線

-initWithDuration:controlPoint1:controlPoint2:animations:

三次貝塞爾曲線的起點為(0,0)且其終點為(1,1),因此兩個控制點的取值范圍是(0,1)。

  • 使用基于彈簧的彈性

-initWithDuration:dampingRatio:animations:

dampingRatio:所對應(yīng)的參數(shù)叫做阻尼,一般去值為(0,1)較低的阻尼值對應(yīng)較小阻力和在靜止之前更多更大的振蕩。反之則阻力大,振蕩少而小。例如你想不振蕩的情況下平滑的減速動畫,就可以指定值為1。

// 創(chuàng)建動畫器
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 協(xié)議的對象,如果你要自定義實現(xiàn)此協(xié)議,必須提供所有屬性的實現(xiàn)。

系統(tǒng)有兩個遵循該協(xié)議的類

UICubicTimingParameters 
UISpringTimingParameters

如果你查看UICubicTimingParameters類時,你會發(fā)現(xiàn),這個類也只是提供了支持 UIKit 內(nèi)置的時間速率曲線和三次貝塞爾曲線。類似的UISpringTimingParameters也提供了CASpringAnimation中的幾個物理參數(shù)。

示例:我們通過該方法實現(xiàn)一下和上一個方法類似的效果

// 彈性的時間速率
UISpringTimingParameters* parameters = [[UISpringTimingParameters alloc] initWithDampingRatio:0.35];
// 創(chuàng)建動畫器
UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 timingParameters:parameters];
// 由于該創(chuàng)建方法沒有動畫塊,因此需要自行追加
[animator addAnimations:^{
    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);
}];
// 開始動畫
[animator startAnimation];
彈性的時間速率

如果說,你受不了每次都需要主動調(diào)用-startAnimation方法來啟動視圖動畫,還是習(xí)慣 UIView 的快捷使用!??,蘋果似乎注意到了這一點,為了適應(yīng)開發(fā)者的習(xí)慣,除了上述幾種創(chuàng)建動畫器的方式,還有一種可以啟動開啟動畫并能返回當(dāng)前動畫器的方法。

  • 類方法便捷創(chuàng)建

+ runningPropertyAnimatorWithDuration:delay:options:animations:completion:

該方法提供了動畫的幾個相對比較重要的參數(shù),如動畫執(zhí)行時間、延遲時間、時間速率、動畫塊、完成塊。該方法兼容了 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 協(xié)議,而UIViewImplicitlyAnimating 協(xié)議是 UIViewAnimating 協(xié)議的子類,該類定義了如何控制動畫的協(xié)議。除了上一節(jié)中使用到的-startAnimation方法,還有其他幾個控制動畫的方法。

  • 開始執(zhí)行動畫

-startAnimation:方法可以啟動動畫或者在暫停動畫后恢復(fù)動畫。
-startAnimationAfterDelay::和上面方法類似,不過可以指定延遲執(zhí)行的時間

  • 暫停動畫

-pauseAnimation:暫停動畫,當(dāng)使用該方法后,動畫會停留在“當(dāng)前位置”,會保持當(dāng)前的狀態(tài)。暫停后可以使用-startAnimation恢復(fù),恢復(fù)的動畫會從“當(dāng)前位置”繼續(xù)剩余的動畫,包括剩余的時間。

示例:我們執(zhí)行一個2秒時長的動畫,在1秒處停止,延遲1秒后恢復(fù)動畫,讓其繼續(xù)執(zhí)行。

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(), ^{
    // 暫停當(dāng)前動畫
    [animator pauseAnimation];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 恢復(fù)動畫
        [animator startAnimation];
    });
});
動畫的暫停和恢復(fù)
  • -stopAnimation::停止動畫

停止動畫有多種情況,由于動畫狀態(tài)的機(jī)制(進(jìn)階篇會講)的存在,當(dāng)我們停止動畫后,這些動畫狀態(tài)信息何去何從?蘋果給出了兩種的去處,一種時清除所有狀態(tài)信息,動畫器重置為初始的非活躍狀態(tài),以等待下一個動畫;另外一種是保留所有狀態(tài)信息,等待下一步操作。這里的 withoutFinishing 參數(shù)就是用來指明去處。

參數(shù) withoutFinishing,表示是否應(yīng)執(zhí)行任何最終操作。如果值為 YES,則會清除任何動畫并將動畫器重置為非活躍狀態(tài),并且不會執(zhí)行完成塊的回調(diào)。

示例:我們執(zhí)行一個2秒的動畫,在一秒處停止當(dāng)前動畫,并且在完成塊中將視圖的背景色更改為紅色。

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) {
    // 動畫的完成回調(diào)
    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的情況

運行結(jié)果發(fā)現(xiàn),動畫在1秒處停止了,但是并沒變成紅色背景,這說明,此時的動畫器并不會執(zhí)行完成塊。

當(dāng)參數(shù)為 NO 時,動畫器狀態(tài)為 stopped,此時通常會配合finishAnimationAtPosition:使用,該方法可以幫助動畫器執(zhí)行最終的完成塊的內(nèi)容,當(dāng)然,這兩個方法的目的是停下當(dāng)前動畫,讓你完成此刻需要完成的內(nèi)容,如其他動畫,之后,你再使用finishAnimationAtPosition:完成動畫的回調(diào)以及動畫需要停止的位置。

在演示示例之前,我們來介紹一下finishAnimationAtPosition:。

  • -finishAnimationAtPosition::結(jié)束動畫

該方法可以將處于 stopped 狀態(tài)的動畫重置為非活躍狀態(tài),并執(zhí)行動畫的完成塊。

此方法通常配合 -stopAnimation: 使用,并且該方法必須在動畫器狀態(tài)為 stopped 狀態(tài)才可以,否則會出現(xiàn)錯誤。該方法的 UIViewAnimatingPosition 參數(shù)有一下三種:

UIViewAnimatingPositionEnd //動畫的終點位置
UIViewAnimatingPositionStart //動畫的開頭位置
UIViewAnimatingPositionCurrent //動畫當(dāng)前位置

指定 UIViewAnimatingPositionCurrent 以使視圖屬性與其當(dāng)前值保持不變。

示例:我們繼續(xù)之前的例子,這次我們配合 -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) {
    // 動畫的完成回調(diào)
    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];
    // 動畫器的狀態(tài)必須是stopped
    if (animator.state==UIViewAnimatingStateStopped) {
        [animator finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
    }
});
withoutFinishing為NO的情況

我們發(fā)現(xiàn),動畫運行1秒后停止了,并且背景色被填充為紅色,這說明-finishAnimationAtPosition:觸發(fā)完成塊,這一點和之前的例子是不同的。另外,我們看到視圖停下來之后就保持在了當(dāng)前位置,這是因為我們給的結(jié)束位置就是 Current。下圖演示了位置的不同參數(shù)的效果。

start、current、end三種不同位置

交互式動畫

fractionComplete 屬性

UIViewPropertyAnimator 類中有一個fractionComplete屬性,這個屬性表示當(dāng)前動畫的完成的百分比,并且這個屬性不是只讀的屬性,這說明我們可以精準(zhǔn)的控制動畫的整個過程。利用它,我們可以制作交互式動畫。交互式動畫的好處是:對于多個視圖、非常復(fù)雜的視圖變化加以控制變得簡單。

示例:

@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;
        // 藍(lán)色視圖
        
        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之前,最好調(diào)用-pauseAnimation暫停當(dāng)前動畫,此時動畫處于活躍狀態(tài),但非isRunning。

修改動畫

正如前面簡介中提到過,UIViewPropertyAnimator 可以修改動畫,甚至是在動畫處于運行狀態(tài)。我們可以添加多個動畫塊、完成塊,設(shè)置是暫停掉正在執(zhí)行的動畫,并且修改它的剩余時間,這讓我們更加的精準(zhǔn)的控制視圖的動畫行為。

  • -addAnimations::為視圖添加動畫塊

我們之前在使用自定義時間速率對象初始化動畫器時,曾經(jīng)使用到過該方法,此方法可以讓我們對視圖的動畫追加多個動畫塊。

示例:我們?yōu)檎谶\動的視圖添加漸變動畫

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秒內(nèi)完成了漸變效果。

當(dāng)然,蘋果已經(jīng)給出了類似的方法,無需我們主動寫延遲方法。就像下面的演示。

  • -addAnimations:delayFactor::延遲追加動畫塊

參數(shù)delayFactor是指時間因子,即動畫的進(jìn)度,取值區(qū)間為(0,1)。比如,0.5表示動畫執(zhí)行一半的時候執(zhí)行。

示例:我們使用該方法完成之前的例子

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::追加完成塊

既然動畫塊都可以追加修改,那么完成塊也應(yīng)該相應(yīng)的有追加方法呀!

在初始化以動畫器一節(jié)中,我們發(fā)現(xiàn)大部分都是不帶有完成塊回調(diào)的,蘋果似乎考慮到開發(fā)過程中很少會關(guān)心動畫的完成事件吧,因此為了方法的簡潔性,就讓其變成了可選特性,又或者這樣設(shè)計會讓動畫變得更加的靈活,因為這樣,動畫的完成事件就無需緊跟在初始化方法上了。

示例:我們在動畫執(zhí)行完成時執(zhí)行一些事情

這里為了方便看到效果,我們就直接來改變視圖的顏色。

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;
}];
追加完成塊

在【控制動畫】一節(jié)中,我們提到過,我們可以調(diào)用-startAnimation:來恢復(fù)暫停后的動畫,但是這樣做的話,動畫的形式依舊是之前設(shè)置好的情況,它并不會發(fā)生變化。那么,如果想要暫定動畫后,執(zhí)行其他時間速率的動畫該怎么辦呢? UIViewPropertyAnimator 可以讓我們?nèi)我獾目刂苿赢?,必然會提供該類方法?/p>

  • -continueAnimationWithTimingParameters:durationFactor::暫停后修改動畫方式繼續(xù)執(zhí)行

該類方法只會在調(diào)用-pauseAnimation方法之后起到作用,此時的動畫狀態(tài)為,活躍但非isRunning

參數(shù)durationFactor是時間因子,表示動畫的進(jìn)度。通??梢匀?code>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];
    // 暫停動畫之后,修改動畫時間速率后繼續(xù)動畫
    UISpringTimingParameters* timing = [[UISpringTimingParameters alloc] initWithDampingRatio:0.2];
    [animator continueAnimationWithTimingParameters:timing durationFactor:animator.fractionComplete];
});
中途更改動畫

上圖演示了中途更改的動畫情況,其中,底下的視圖為參考視圖,即未改變的勻速運動。

以上就是快速入門使用的所有教程了,相信經(jīng)過一系列的介紹之后,你能夠快速的使用新的動畫方式了。

接下來的進(jìn)階篇,會講解一些 UIViewPropertyAnimator 的一些細(xì)節(jié)部分。

進(jìn)階篇

動畫協(xié)議

其實要介紹 UIViewPropertyAnimator 類前,應(yīng)該先介紹其遵循的動畫協(xié)議--UIViewAnimating 和 UIViewImplicitlyAnimating 。前者是后者的父類,我們先來解釋 UIViewAnimating 協(xié)議。該協(xié)議定義了操作動畫的基本方法,包括啟動、停止、暫停動畫的能力。另外還有幾個屬性用于反映動畫的當(dāng)前狀態(tài)信息。

UIViewPropertyAnimator 遵循并實現(xiàn)了 UIViewAnimating 協(xié)議的所有方法,因此我們可以用 UIViewPropertyAnimator 來實現(xiàn)動畫的控制。如果你想在自定義類中也遵循此協(xié)議,最好實現(xiàn)所有的協(xié)議方法和屬性。

動畫的狀態(tài)

UIViewPropertyAnimator 有著一套完整的狀態(tài)機(jī)制。在動畫器處理一組動畫時,都會伴隨著這一系列的動畫狀態(tài)。這些狀態(tài)定義了動畫器的行為,包括它是如何處理變化的。如果你在實現(xiàn)自定義動畫器,必須遵循這些狀態(tài)的轉(zhuǎn)換并準(zhǔn)確的更新狀態(tài)屬性。下圖顯示了發(fā)生的狀態(tài)和狀態(tài)轉(zhuǎn)換關(guān)系。

狀態(tài)轉(zhuǎn)換關(guān)系

Inactive非活躍狀態(tài)是動畫器的初始狀態(tài)。每個新創(chuàng)建的動畫器都會處于非活躍狀態(tài)下啟動。相對的,動畫正常完成后會返回到非活躍狀態(tài)。

當(dāng)我們調(diào)用-startAnimation-pauseAnimation方法時,此時動畫器會變?yōu)?code>Active活躍狀態(tài)。此狀態(tài)下的動畫器正在運行或暫停狀態(tài)。如果是動畫被暫停,我們此時還可以修改動畫時間速率曲線,讓后讓其繼續(xù)運行到預(yù)期結(jié)束,結(jié)束后的動畫器狀態(tài)依舊是Inactive非活躍狀態(tài),等待我們使用一組新的動畫重新配置它,以便開始新的動畫。

當(dāng)我們開啟動畫之后,調(diào)用-stopAnimation:方法會停止正在運行的動畫,此時視圖會被保留在停止的那一刻的值。此方法的參數(shù)值決定了當(dāng)前的動畫信息是否被擦除。如果參數(shù) withoutFinishing 是 YES,則表示擦除當(dāng)前動畫的信息,動畫器進(jìn)入Inactive狀態(tài),需要注意,這種情況下,動畫器是不會執(zhí)行完成塊的,換句話說你無法在完成塊中得到動畫結(jié)束信息,此時如果你需要知道動畫結(jié)束的事件,你可以使用 KVO 的方法監(jiān)聽屬性isRunning獲得。如果參數(shù) withoutFinishing 是 NO,則表示保留當(dāng)前動畫的信息,動畫器進(jìn)入Stopped狀態(tài),此時我們可以去完成其他的操作,如執(zhí)行其他動畫。然后我們調(diào)用方法-finishAnimationAtPosition:以結(jié)束此次動畫,動畫器順理成章的進(jìn)入到Inactive狀態(tài)。注意,這情情況下,動畫器可以順利的執(zhí)行完成塊內(nèi)容。

動畫狀態(tài)的幾個枚舉:

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) ;

協(xié)議內(nèi)容

方法

  • -startAnimation 開始動畫

不可以在動畫器調(diào)用方法-stopAnimation:直接結(jié)束動畫后再次調(diào)用-startAnimation,換句話說,使用過程中,出現(xiàn)下面情況會出錯:

錯誤
錯誤

我們發(fā)現(xiàn),在我們-stopAnimation:指定參數(shù)為 YES時,動畫器狀態(tài)由活躍狀態(tài)Active轉(zhuǎn)變?yōu)榉腔钴S狀態(tài)Inactive,此時再次調(diào)用-startAnimation時,系統(tǒng)拋出了異常。

而我們指定參數(shù)為 NO時,

正常

此時動畫器狀態(tài)為stopped,程序并未出錯。

正常

這一點和官方文檔的說明并不一致,目前還不是很清楚原因。

It is a programmer error to call this method while the state of the animator is set to UIViewAnimatingStateStopped.

??:11-29,以上結(jié)論基于10.3.2系統(tǒng),但是筆者使用11以上的系統(tǒng)發(fā)現(xiàn),結(jié)論和上述相反,卻和官方文檔一致,即動畫器狀態(tài)為stopped下,不能使用-startAnimation。這一點讓我更加凌亂了,難道后面的系統(tǒng)修正了?

  • -startAnimationAfterDelay: 延遲后開始動畫

上面的開啟動畫一樣的注意點同樣適用。(請注意上面 11-29 的說明)

另外經(jīng)測試發(fā)現(xiàn),-pauseAnimation之后調(diào)用-startAnimationAfterDelay:會發(fā)生程序錯誤。

  • -pauseAnimation 暫停動畫

暫停動畫后,可以使用-startAnimation方法重新恢復(fù)動畫,另外你也可以使用協(xié)議UIViewImplicitlyAnimating中的continueAnimationWithTimingParameters:durationFactor:恢復(fù)動畫。如果動畫已經(jīng)暫停,則再次調(diào)用-pauseAnimation不會執(zhí)行任何操作。

經(jīng)測試發(fā)現(xiàn)如果動畫器從未啟動過,直接調(diào)用-pauseAnimation方法,如果緊接著調(diào)用-startAnimation或者continueAnimationWithTimingParameters:durationFactor:是無法恢復(fù)動畫的,之間需要大于千分之一秒的時間,就像下面的情況:

無法恢復(fù)動畫的情況:

[self.animator pauseAnimation];
[self.animator startAnimation];
//[self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5];

如果之前調(diào)用過開啟動畫,則可以恢復(fù)動畫

[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狀態(tài)時,調(diào)用-pauseAnimation會出現(xiàn)程序錯誤,但并沒有。

??:11-29,以上結(jié)論基于10.3.2系統(tǒng),使用11以上的系統(tǒng)發(fā)現(xiàn)并不會出現(xiàn)【無法啟動暫停動畫】的情況,并且,動畫器為stopped狀態(tài)時,調(diào)用-pauseAnimation時,確實出現(xiàn)程序錯誤。

  • -stopAnimation: 停止動畫

需要注意的是,不可以在動畫器狀態(tài)由Active轉(zhuǎn)為為stopped的時候再調(diào)用該方法。下面的使用會發(fā)生程序錯誤:

錯誤使用

上圖中的調(diào)用-pauseAnimation將動畫器轉(zhuǎn)為Active也會出現(xiàn)錯誤。

這一點,官方文檔卻并未提及。why?

  • -finishAnimationAtPosition: 結(jié)束動畫

該方法通常會和上面的停止方法相結(jié)合,用來結(jié)束當(dāng)前停止下來(狀態(tài)為stopped)動畫順利回到Inactive狀態(tài)。經(jīng)測試,該方法只認(rèn)stopped狀態(tài),其他兩個狀態(tài)都會發(fā)生錯誤。

屬性

  • fractionComplete動畫執(zhí)行的進(jìn)度

描述了當(dāng)前動畫的進(jìn)度,可被更改,當(dāng)動畫處于停止時,可配合手勢等實現(xiàn)交互式動畫。

  • reversed是否可以反轉(zhuǎn)動畫

關(guān)于反轉(zhuǎn)動畫,目前還未知如果實現(xiàn)反轉(zhuǎn)動畫。

  • state動畫器的狀態(tài)

  • running動畫的運行狀態(tài),支持KVO

修改動畫協(xié)議

UIViewImplicitlyAnimating 繼承自 UIViewAnimating 協(xié)議,在后者協(xié)議的基礎(chǔ)上又添加了一些額外的修改動畫的方法。而我們使用的 UIViewPropertyAnimator 動畫器就遵循了這個相對完善的協(xié)議,并實現(xiàn)了所有的方法。

方法

  • -addAnimations: 添加動畫塊

使用此方法可以將新的動畫塊添加到自定義動畫對象。新的動畫會與先前的動畫一起運行,并從當(dāng)前時間開始并與任何原始動畫同時結(jié)束。

  • -addAnimations:delayFactor:添加延遲動畫塊

同上,不過會從指定的延遲開始并與任何原始動畫同時結(jié)束。

參數(shù) delayFactor:用于延遲動畫開始的時間因子。該值必須介于0.0和1.0之間。將此值乘以動畫剩余持續(xù)時間,作為實際延遲。例如,如果值0.5、動畫器的持續(xù)時間為2.0,則延遲一秒執(zhí)行動畫。

  • -addCompletion:添加動畫完成塊

回調(diào)動畫完成的事件,你可以在該 block 中完成其他操作。

參數(shù) withoutFinishing 有三種,表示最后動畫結(jié)束的位置。

typedef NS_ENUM(NSInteger, UIViewAnimatingPosition) {
    UIViewAnimatingPositionEnd,
    UIViewAnimatingPositionStart,
    UIViewAnimatingPositionCurrent,
} NS_ENUM_AVAILABLE_IOS(10_0);

如果動畫正常完成結(jié)束,位置參數(shù)為UIViewAnimatingPositionEnd,即最終的期望位置;

如果動畫執(zhí)行過程中,調(diào)用了-stopAnimation:,并且制定的參數(shù)為 YES,動畫器則不會調(diào)用完成塊;

如果動畫執(zhí)行過程中,調(diào)用了-stopAnimation:,并且制定的參數(shù)為 NO,此時需要調(diào)用finishAnimationAtPosition:配置結(jié)束動畫,此時完成塊中的位置參數(shù)由方法finishAnimationAtPosition:決定。

  • -continueAnimationWithTimingParameters:durationFactor:調(diào)整暫停的動畫的時間速率曲線和持續(xù)時間

參數(shù) parameters 是指時間速率曲線,系統(tǒng)提供了兩種,兼容了 UIKit 內(nèi)置的四種時間速率曲線、三次貝塞爾曲線、彈簧式的彈性動畫。

UICubicTimingParameters
UISpringTimingParameters

參數(shù) durationFactor 是指動畫原始持續(xù)時間的因子,取值為(0,1),將此值乘以動畫的原始持續(xù)時間,作為新的持續(xù)時間。

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];
});
修改動畫持續(xù)時間

如果我們將 durationFactor 傳遞為當(dāng)前動畫器的進(jìn)度值 fractionComplete ,你會發(fā)現(xiàn)執(zhí)行的動畫并沒有什么變化,這是因為 fractionComplete 的值乘以原始持續(xù)時間就等于動畫剩余的時間。

但是我們將值放大,比 fractionComplete 的值要大,那么動畫的剩余時間就會被拉長,剩下的動畫會在新的時間內(nèi)完成。

示例:我們將動畫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];
});
修改剩余動畫的持續(xù)時間

當(dāng)前就像之前介紹的,你也可以改換當(dāng)前動畫的時間速率曲線,或者更換為彈性動畫。

動畫器

介紹完 UIViewPropertyAnimator 的兩種協(xié)議之后,我們來看下 UIViewPropertyAnimator 中的一些其小細(xì)節(jié)。

三次貝塞爾曲線

在構(gòu)建動畫器的方法中,有一個之前我們一提而過的方法:

-initWithDuration:controlPoint1:controlPoint2:animations:

這個方法可以讓我們自定義時間速率曲線,采用的是三次貝塞爾曲線,可能有些人并不清楚什么是三次貝塞爾曲線。三次貝塞爾曲線在繪制圖形時經(jīng)常出現(xiàn),是有起點、終點以及兩個控制點產(chǎn)生的曲線。文字描述比較抽象,我們來看下圖:

三次貝塞爾曲線

該曲線的起點為(0,0),其終點為(1,1)。point1 和 point2 參數(shù)是定義生成的貝塞爾曲線形狀的控制點。其中,起點和控制點1的連線為曲線的切線,終點和控制點2的連線也是曲線的切線,控制點1和控制點2的連寫也是曲線的切線,這樣,產(chǎn)生的曲線就是三次貝塞爾曲線。

該曲線的斜率定義了動畫的不同時間速率,斜率越大,速度越快,斜率越小速度越慢。上圖顯示了一個速率曲線,其中動畫快速啟動并快速完成,但在中間部分運行得相對較慢。

屬性

  • duration 只讀

動畫的持續(xù)時間,只有在初始化動畫器時指定該值,稍后添加的動畫僅在剩余的時間內(nèi)運行。剩余時間由公式(1.0 - fractionComplete)* 持續(xù)時間確定。

  • delay 只讀

延遲動畫時間,默認(rèn)值為0。如果要為此屬性設(shè)置值,啟動動畫時則需要使用startAnimationAfterDelay:方法

  • timingParameters 只讀

描述速度的曲線,和duration一樣,只有在初始化 animator 指定該值??梢允褂么藢傩陨院螳@取這些參數(shù)。

  • interruptible

動畫中是否可被打斷。當(dāng)此屬性的值為 YES 時,我們可以使用-pauseAnimation-stopAnimation:方法來中斷動畫并進(jìn)行更改。當(dāng)此屬性的值為 NO 時,在調(diào)用startAnimation方法后,動畫將運行至完成(并且不會中斷)。

如果使用動畫器來實現(xiàn)可中斷的視圖控制器轉(zhuǎn)換,則此屬性必須為 YES。

  • userInteractionEnabled

動畫中用戶是否可交互。默認(rèn)值為 YES。當(dāng)此屬性的值為 YES 時,觸摸事件將正常傳遞給視圖,否則在動畫持續(xù)時間內(nèi)會忽略用戶的觸摸事件。

  • manualHitTestingEnabled

動畫中點擊測試的能力。默認(rèn)為 NO。

  • scrubsLinearly

暫停的動畫是否使用線性擦除或者使用指定的時間速率曲線。iOS11之后可用。

  • pausesOnCompletion

動畫完成后是否保持活動狀態(tài)。默認(rèn)值為 NO。iOS11之后可用。

當(dāng)此屬性的值為 YES 時,動畫器完成后動畫后將保持Active狀態(tài),并且不會執(zhí)行完成塊。此時我們可以撤消動畫。當(dāng)此屬性的值為 NO 時,動畫完成后,動畫器執(zhí)行完成塊,自動轉(zhuǎn)換為Inactive狀態(tài),從而結(jié)束動畫。

注:由于 YES 的情況下,動畫器并且不會執(zhí)行完成塊,因此如果你想要知道動畫的結(jié)束事件,你需要監(jiān)聽動畫器的running屬性。

補充

  • 設(shè)置同一可動畫屬性

-addAnimations:方法可以讓我們添加多個屬性動畫塊,那么,如果兩個或多個動畫需要同時改變相同的屬性會發(fā)生什么呢?蘋果采用的是“后者優(yōu)先”原則。即:后添加的動畫效果會覆蓋之前的動畫效果。但有趣的是,這將導(dǎo)致卡頓,因為需要組合新舊動畫,在舊動畫淡出的同時會隱約看見新動畫。

示例:

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);
}];
設(shè)置同一動畫屬性

我們發(fā)現(xiàn),后添加的縮小為0.5的動畫效果覆蓋了之前的放大為1.5的動畫效果。但這似乎看不出所為卡頓的效果,那我們來看下填充背景色會發(fā)生什么。

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;
}];
設(shè)置同一動畫屬性

該示例中,視圖的最初顏色為藍(lán)色,在第一個動畫塊中,我們將其設(shè)置為紅色,后又設(shè)置為黃色。在該動畫運行過程中,我們發(fā)現(xiàn),視圖立刻被設(shè)置為紅色,然后由紅色漸變?yōu)辄S色。因此,在多個動畫塊中設(shè)置同一個動畫屬性并不可控,我們應(yīng)該盡可能的避免這種情況的出現(xiàn)。


總結(jié)

UIViewPropertyAnimator 類讓我們能夠精準(zhǔn)的控制視圖動畫的每個細(xì)節(jié)。我們可以使用該類完成各種動畫的設(shè)置,中途修改動畫,甚至可以便捷的完成交互性的動畫,這徹底改變了我們設(shè)置視圖動畫的習(xí)慣,這些改變令人驚喜萬分。

在進(jìn)階篇中,我們發(fā)現(xiàn)了很多異常的情況,并且在不同的系統(tǒng)上有著不同的表現(xiàn),甚至是完全相反的情況,這一點讓人非常的疑惑,筆者猜想可能是蘋果在 iOS11 系統(tǒng)之后改變了 UIViewPropertyAnimator 的一些實現(xiàn)細(xì)節(jié)部分,導(dǎo)致了前后不一致的情況,但是在這種情況下,想要使用該類需要異常謹(jǐn)慎。

但是,不能夠因噎廢食,如果我們開發(fā)過程中能夠注意到這些異常的情況,避免這些異常操作,UIViewPropertyAnimator 不失為一個較為良好的動畫類。

根據(jù)之前的問題,有幾點建議:

  • 創(chuàng)建完動畫器之后,請使用-startAnimation-startAnimationAfterDelay:方法開啟動畫,或者直接使用+ runningPropertyAnimatorWithDuration:delay:options:animations:completion:

  • stopped情況下,請配合-finishAnimationAtPosition:方法結(jié)束后續(xù)動畫,而非其他方法

  • 在暫停動畫的情況下,請使用-startAnimation-continueAnimationWithTimingParameters:durationFactor:方法恢復(fù)動畫,可能的話,請保證動畫是由運行中暫停的,或者延遲大于千分之秒的時間恢復(fù)動畫


相關(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)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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