在iOS 10之前,執行動畫可以使用UIView
以下三個類方法:
animateWithDuration: animations:
animateWithDuration: animations: completion:
animateWithDuration: delay: options: animations: completion:
上面創建動畫塊(animations: )的均是類方法,所以這些動畫塊沒有綁定任何視圖。因此,可以使用這些方法創建一個單一的動畫,應用于多個視圖。
下面代碼演示了在1秒鐘內淡出firstView
、淡入secondView
的操作。
[UIView animateWithDuration:1.0 animations:^{
self.firstView.alpha = 0.0;
self.secondView.alpha = 1.0;
}];
使用上述類方法操縱動畫過程中,firstView
和secondView
兩個視圖將暫時禁用用戶交互,即用戶不能與正在運行動畫的視圖交互。如果想要實現更復雜的功能,需要使用CoreAnimation
框架。雖然使用CoreAnimation
框架可以對動畫進行暫停、恢復、停止等操作,但會產生大量代碼,需要大量工作,可以使用pop一類的第三方庫。
1. UIViewPropertyAnimator
在iOS 10,Apple在UIKit
中增加了UIViewPropertyAnimator
類,使用該類實現的動畫可以在結束前進行暫停、恢復、停止等操作。UIViewPropertyAnimator
類對象通過操作視圖的屬性來產生所需動畫,但不是所有屬性的改變都會產生動畫,可產生動畫的屬性包括frame
、center
、alpha
和transform
。
UIViewPropertyAnimator
類遵守了UIViewAnimating
和UIViewImplicitlyAnimating
協議。UIViewAnimating
協議中方法用于控制動畫的狀態,包括開始startAnimation
、暫停pauseAnimation
、結束stopAnimation:
動畫,也有一些屬性用于反映當前動畫狀態。動畫運行時,這些狀態隨之更新。UIViewPropertyAnimator
對象(后面稱為animator)在處理動畫期間會經歷UIViewAnimatingStateInactive
、UIViewAnimatingStateActive
、UIViewAnimatingStateStopped
不同狀態。下圖顯示了animator狀態變化。
Inactive狀態是animator的初始狀態。每一個新創建的animator均處于inactive狀態,animator在動畫結束后返回到inactive狀態。在非活躍狀態配置的動畫,其運行持續時間為指定的完整持續時間;使用addAnimations:
方法添加的動畫,添加后立即開始執行,與其它動畫同時結束,即運行時間為剩余持續時間,非完整持續時間。
當調用startAnimation
或pauseAnimation
方法后,animator變為active狀態。處于active狀態的animator可能正在運行動畫,也可能將動畫暫停,以便修改動畫。當動畫運行到指定位置結束后,animator將返回到inactive狀態,以便重新配置動畫。
調用stopAnimation:
方法后,將停止所有正在運行的動畫,并將相應視圖屬性的值更新為調用stopAnimation:
方法時的值。當stopAnimation:
參數為YES
時,animator狀態直接改變為UIViewAnimatingStateInactive
,且不再執行任何動作;當stopAnimation:
參數為NO
時,animator狀態改變為UIViewAnimatingStateStopped
,隨后,可以調用finishAnimationAtPosition:
方法執行animator的最終動作。例如,執行completion塊。對finishAnimationAtPosition:
方法的調用不是必須的,也可以在調用finishAnimationAtPosition:
方法前先執行其它動畫。
UIViewImplicitlyAnimating
協議用于在動畫進行過程中修改動畫,如添加動畫addAnimations:
、添加completion塊addCompletion:
等。
2. 示例1
下面通過一個demo來學習UIViewPropertyAnimator
。
創建Single View Application模板的應用,demo名稱為PropertyAnimator。在storyboard中添加一個UIView
、三個UIButton
、兩個UILabel
、一個UISwitch
、一個UIStepper
和一個UISlider
。布局如下:
使用Auto Layout、Stack View對視圖進行自動布局,通過上圖可以看到所添加的約束,其中Stack View的spacing均為
20
。如果你對自動布局不熟悉,可以查看Auto Layout的使用和Auto Layout中Stack View的使用兩篇文章。
從storyboard向ViewController.m
添加IBOutlet屬性。如下所示:
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *redView;
@property (weak, nonatomic) IBOutlet UIButton *restartButton;
@property (weak, nonatomic) IBOutlet UIButton *startStopButton;
@property (weak, nonatomic) IBOutlet UIButton *pauseButton;
@property (weak, nonatomic) IBOutlet UISwitch *reverseAnimationSwitch;
@property (weak, nonatomic) IBOutlet UIStepper *durationStepper;
@property (weak, nonatomic) IBOutlet UILabel *durationLabel;
@end
最后再添加以下三個屬性。
@interface ViewController ()
...
@property (strong, nonatomic) UIViewPropertyAnimator *propertyAnimator;
@property (assign, nonatomic) NSTimeInterval duration;
@property (assign, nonatomic) CGRect startFrame;
@end
現在添加一個setupAnimator
的方法,用于配制動畫。
- (void)setupAnimator {
// 1.動畫初始位置。
self.redView.frame = self.startFrame;
// 2.動畫終點位置。
CGFloat margin = 16.0;
CGFloat screenWidth = CGRectGetWidth(self.view.frame);
CGFloat finalX = screenWidth - CGRectGetWidth(self.redView.frame) - margin;
CGRect finalRect = CGRectMake(finalX, self.redView.frame.origin.y, self.redView.frame.size.width, self.redView.frame.size.height);
// 3.初始化動畫。
self.propertyAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:self.duration
curve:UIViewAnimationCurveEaseIn
animations:^{
self.redView.frame = finalRect;
}];
// 4.為動畫添加完成塊。
ViewController * __weak weakSelf = self;
ViewController *vc = weakSelf;
if (vc) {
[self.propertyAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) {
[vc.startStopButton setTitle:@"Start" forState:UIControlStateNormal];
vc.startStopButton.enabled = NO;
vc.pauseButton.enabled = NO;
vc.restartButton.enabled = YES;
}];
}
vc = nil;
}
在上面的代碼中,注釋3部分用于初始化動畫,其中initWithDuration:
的參數為動畫持續時間;curve:
參數用來設定動畫曲線。這里有以下四種可選曲線:
- UIViewAnimationCurveEaseInOut:這種曲線的動畫開始緩慢,在其持續時間的中間加速,然后在完成之前再次減慢。這是大多數動畫的默認曲線。
- UIViewAnimationCurveEaseIn:動畫開始時緩慢,然后加速,直到動畫結束。這里選用這種類型動畫曲線。
- UIViewAnimationCurveEaseOut:動畫開始時速度很快,在結束前開始減速。
- UIViewAnimationCurveLinear:在動畫持續時間內,動畫勻速運行。
最后在動畫塊內配置動畫。注釋4部分,為動畫塊添加完成回調函數。這里需要注意避免形成循環引用,使用__weak
聲明的weakSelf
替代self
,因為塊內多次用到weakSelf
,可能在第一次調用weakSelf
時weakSelf
存在,但后面再次調用weakSelf
時,weakSelf
已被釋放,所以這里再次將其轉換為強引用類型vc
,不再需要vc
時,需要手動釋放。
想要了解更多關于塊的用法,查看Block的用法一文。
從storyboard中選中UIStepper
控件,拖拽到代碼中,以添加響應事件。
- (IBAction)stepperValueChanged:(UIStepper *)sender {
// 1.設置動畫持續時間為UIStpper的值。
self.duration = sender.value;
// 2.同步更新到label。
self.durationLabel.text = [NSString stringWithFormat:@"%.1f",sender.value];
}
上述代碼用于設定動畫持續時間。
在storyboard中選中Start按鈕,拖拽到ViewController.m
實現部分,以添加響應事件,響應事件方法名稱為startStopPause
。在storyboard中選中Pause按鈕,也拖拽到startStopPause:
響應事件,以便在點擊Start或Pause任一按鈕時,均調用startStopPause:
方法。
- (IBAction)startStopPause:(UIButton *)sender {
// 1.禁用restartButton,propertyAnimator不存在時,配置propertyAnimator。
self.restartButton.enabled = NO;
if (!self.propertyAnimator) {
[self setupAnimator];
}
if (sender == self.pauseButton) {
// 2.點擊Pause按鈕時,根據當前動畫狀態更新視圖。
self.propertyAnimator.isRunning ? [self.propertyAnimator pauseAnimation] : [self.propertyAnimator startAnimation];
NSString *title = self.propertyAnimator.isRunning ? @"Pause" : @"Unpause";
[self.pauseButton setTitle:title forState:UIControlStateNormal];
self.startStopButton.enabled = self.propertyAnimator.isRunning;
}
else
{
// 3.點擊Start按鈕時,根據當前動畫狀態更新視圖。
switch (self.propertyAnimator.state) {
case UIViewAnimatingStateInactive:
[self.propertyAnimator startAnimation];
[self.startStopButton setTitle:@"Stop" forState:UIControlStateNormal];
self.pauseButton.enabled = YES;
NSLog(@"UIViewAnimatingStateInactive");
break;
case UIViewAnimatingStateActive:
if (self.propertyAnimator.isRunning) {
[self.propertyAnimator stopAnimation:NO];
[self.propertyAnimator finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
[self.startStopButton setTitle:@"Start" forState:UIControlStateNormal];
self.pauseButton.enabled = NO;
}
else
{
[self.propertyAnimator startAnimation];
[self.startStopButton setTitle:@"Stop" forState:UIControlStateNormal];
self.pauseButton.enabled = YES;
}
NSLog(@"UIViewAnimatingStateActive");
break;
case UIViewAnimatingStateStopped:
NSLog(@"UIViewAnimatingStateStopped");
break;
default:
break;
}
}
}
上面代碼根據所點擊的按鈕,進行不同的響應操作。當點擊的是Start按鈕時,根據propertyAnimator
所處的狀態執行不同操作。
從storyboard中拖拽UISwitch
控件到代碼中,以創建響應事件。
- (IBAction)reverseAnimationSwitchValueChanged:(UISwitch *)sender {
if (self.propertyAnimator && self.propertyAnimator.isRunning) {
// 在propertyAnimator存在,且正在運行時,根據UISwitch的值調整propertyAnimator的方向。
self.propertyAnimator.reversed = sender.isOn;
}
}
從storyboard中拖拽Restart Animation按鈕到代碼中,以創建響應事件。
- (IBAction)restartAnimation:(UIButton *)sender {
// 點擊RestartAnimation按鈕時,重新配置動畫,啟用Start按鈕,將reverseAnimationSwitch設置為關閉狀態。
[self setupAnimator];
self.startStopButton.enabled = YES;
self.reverseAnimationSwitch.on = NO;
}
可以通過為fractionComplete
屬性賦值,設置動畫進度百分比。從storyboard中拖拽UISlider
控件到代碼中,創建響應事件。
- (IBAction)scrubAnimation:(UISlider *)sender {
if (!self.propertyAnimator) {
[self setupAnimator];
}
// 1.當propertyAnimator正在運行時,暫停該動畫,否則在停止拖動UISlider后,動畫會繼續運行。
if (self.propertyAnimator.isRunning) {
[self.propertyAnimator pauseAnimation];
}
// 2.設置動畫百分比。
self.propertyAnimator.fractionComplete = sender.value;
}
最后,記得設置startFrame
位置、pauseButton
初始狀態。
- (void)viewDidLoad {
[super viewDidLoad];
// 設置startFrame位置,pauseButton狀態。
self.startFrame = CGRectMake(16, 45, 100, 100);
self.pauseButton.enabled = NO;
}
現在運行demo,效果如下。
3. 示例2
UIViewPropertyAnimator
除了可以使用原有的UIViewAnimationCurve
時間曲線函數,還新增了UISpringTimingParameters
、UICubicTimingParameters
兩個時間曲線函數。
添加Cocoa Touch Class模版的文件,其父類為UIViewController
,名稱為TimingCurvesViewController
。在storyboard中添加UIViewController
,并設置其父類為TimingCurvesViewController
。
在剛添加的視圖控制器上添加四個UIView
、四個UILabel
。同時選中兩個視圖控制器,添加UITabBarController
,如下所示:
為TimingCurvesViewController
上的UIView
創建IBOutlet連接,并添加以下幾個屬性。
#import "TimingCurvesViewController.h"
@interface TimingCurvesViewController ()
@property (weak, nonatomic) IBOutlet UIView *redView;
@property (weak, nonatomic) IBOutlet UIView *blueView;
@property (weak, nonatomic) IBOutlet UIView *greenView;
@property (weak, nonatomic) IBOutlet UIView *yellowView;
@property (assign, nonatomic) CGRect redStartFrame;
@property (assign, nonatomic) CGRect blueStartFrame;
@property (assign, nonatomic) CGRect greenStartFrame;
@property (assign, nonatomic) CGRect yellowStartFrame;
@property (strong, nonatomic) UIViewPropertyAnimator *redAnimator;
@property (strong, nonatomic) UIViewPropertyAnimator *blueAnimator;
@property (strong, nonatomic) UIViewPropertyAnimator *greenAnimator;
@property (strong, nonatomic) UIViewPropertyAnimator *yellowStartAnimator;
@property (strong, nonatomic) NSTimer *timer;
@property (assign, nonatomic) NSTimeInterval durationOfAnimation;
@end
在TimingCurvesViewController.m
的viewDidLoad
方法中,設置四個UIView
的初始位置和durationOfAnimation
值。
- (void)viewDidLoad {
[super viewDidLoad];
// 1.設置UIView初始位置。
self.redStartFrame = CGRectMake(16, 50, squareSize, squareSize);
self.blueStartFrame = CGRectMake(16, 150, squareSize, squareSize);
self.greenStartFrame = CGRectMake(16, 250, squareSize, squareSize);
self.yellowStartFrame = CGRectMake(16, 350, squareSize, squareSize);
// 2.為durationOfAnimation賦初始值2.0。
self.durationOfAnimation = 2.0;
}
3.1 UIViewAnimationCurve
UIViewAnimationCurve時間曲線函數分為UIViewAnimationCurveEaseInOut
、UIViewAnimationCurveEaseIn
、UIViewAnimationCurveEaseOut
和UIViewAnimationCurveLinear
四種。
在TimingCurvesViewController.m
實現部分添加以下代碼來使用上述四種時間曲線。
// Default Curves
- (void)startAnimationsWithDefaultCurves {
// 1.四個視圖使用不同類型時間曲線函數。
self.redAnimator = [self animatorForView:self.redView startFrame:self.redStartFrame curve:UIViewAnimationCurveLinear];
self.blueAnimator = [self animatorForView:self.blueView startFrame:self.blueStartFrame curve:UIViewAnimationCurveEaseIn];
self.greenAnimator = [self animatorForView:self.greenView startFrame:self.greenStartFrame curve:UIViewAnimationCurveEaseOut];
self.yellowAnimator = [self animatorForView:self.yellowView startFrame:self.yellowStartFrame curve:UIViewAnimationCurveEaseInOut];
}
- (UIViewPropertyAnimator *)animatorForView:(UIView *)view startFrame:(CGRect)startFrame curve:(UIViewAnimationCurve)curve {
// 2.視圖初始位置,動畫結束時視圖位置。
view.frame = startFrame;
CGRect finalRect = [self finalRectWithStartFrame:startFrame];
// 3.配置動畫。
UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:self.durationOfAnimation curve:curve animations:^{
view.frame = finalRect;
}];
return animator;
}
#pragma mark Help Methods
- (CGRect)finalRectWithStartFrame:(CGRect)startFrame {
// 4.計算出動畫結束時位置。
CGFloat margin = 16;
CGFloat screenWidth = CGRectGetWidth(self.view.frame);
CGFloat finalX = screenWidth - margin - squareSize;
CGRect finalRect = CGRectMake(finalX, startFrame.origin.y, squareSize, squareSize);
return finalRect;
}
上述代碼非常簡單,在注釋1部分使用UIViewAnimationCurveLinear
、UIViewAnimationCurveEaseIn
、UIViewAnimationCurveEaseOut
和UIViewAnimationCurveEaseInOut
四種時間曲線。注釋4的方法后面會多次用到。
在TimingCurvesViewController.m
實現部分添加viewDidAppear:
方法,用定時器觸發動畫。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 1.在1.5倍durationOfAnimation時間后,觸發動畫。
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.durationOfAnimation*1.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
// 2.動畫使用默認時間曲線函數。
[self startAnimationsWithDefaultCurves];
// 啟動動畫。
[self.redAnimator startAnimation];
[self.blueAnimator startAnimation];
[self.greenAnimator startAnimation];
[self.yellowAnimator startAnimation];
}];
}
如果使用標準方法(initWith方法)創建動畫,必須通過調用startAnimation
方法開始動畫;如果想要創建動畫后立即開始執行動畫,請使用runningPropertyAnimatorWithDuration: delay: options: animations: completion:
方法。
運行demo,如下所示:
通過上面代碼可以看到:
- UIViewAnimationCurveEaseInOut:這種曲線的動畫開始緩慢,在其持續時間的中間加速,然后在完成之前再次減慢。這是大多數動畫的默認曲線。
- UIViewAnimationCurveEaseIn:動畫開始時緩慢,然后加速,直到動畫結束。
- UIViewAnimationCurveEaseOut:動畫開始時速度很快,在結束前開始減速。
- UIViewAnimationCurveLinear:在動畫持續時間內,動畫勻速運行。
3.2 UICubicTimingParameters
UICubicTimingParameters
允許通過多個控制點來定義三階貝塞爾曲線,該貝塞爾時間曲線起點為(0,0),終點為(1,1),曲線形狀由兩個控制點決定。每個時間點線的斜率定義了此時動畫速度。曲線越陡峭,動畫運行速度越快;曲線越平緩,動畫運行速度越慢。
下圖的時間曲線表示動畫在開始和結束時速度很快,中間運行較慢。
控制點范圍為0.0至1.0。
UICubicTimingParameters
遵守UITimingCurveProvider
協議,為遵守UIViewAnimating
協議的對象提供時間曲線(timing curves),如UIViewPropertyAnimator
。
繼續更新TimingCurvesViewController.m
內代碼,使用UICubicTimingParameters
作為動畫時間參數。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 1.在1.5倍durationOfAnimation時間后,觸發動畫。
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.durationOfAnimation*1.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
// 2.動畫使用默認時間曲線函數。
// [self startAnimationsWithDefaultCurves];
// 3.使用UICubicTimingParameters
[self startAnimationWithCubicCurves];
// 啟動動畫。
[self.redAnimator startAnimation];
[self.blueAnimator startAnimation];
[self.greenAnimator startAnimation];
[self.yellowAnimator startAnimation];
}];
}
// UICubicTimingParameters
- (void)startAnimationWithCubicCurves {
// 1.中間慢,開始、結尾快。
UICubicTimingParameters *redCubicTimingParameters = [[UICubicTimingParameters alloc] initWithControlPoint1:CGPointMake(0.45, 1.0) controlPoint2:CGPointMake(0.55, 0)];
// 2.中間快,開始、結尾慢。
UICubicTimingParameters *blueCubicTimingParameters = [[UICubicTimingParameters alloc] initWithControlPoint1:CGPointMake(1.0, 0.45) controlPoint2:CGPointMake(0, 0.55)];
self.redAnimator = [self animatorForView:self.redView startFrame:self.redStartFrame timingParameters:redCubicTimingParameters];
self.blueAnimator = [self animatorForView:self.blueView startFrame:self.blueStartFrame timingParameters:blueCubicTimingParameters];
}
- (UIViewPropertyAnimator *)animatorForView:(UIView *)view startFrame:(CGRect)startFrame timingParameters:(UICubicTimingParameters *)cubicTimingParameters {
view.frame = startFrame;
CGRect finalRect = [self finalRectWithStartFrame:startFrame];
UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:self.durationOfAnimation timingParameters:cubicTimingParameters];
[animator addAnimations:^{
view.frame = finalRect;
}];
return animator;
}
其中,startAnimationWithCubicCurves
方法內,注釋1中的控制點決定動畫曲線,動畫在中間慢,開始和結尾處快。注釋2相反。這里只使用了redView
和blueView
兩個視圖。
運行如下:
3.3 UISpringTimingParameters
由UISpringTimingParameters
提供的時間曲線會讓動畫行為與彈簧相似。視圖會加速向目標點運動,然后圍繞該目標點震蕩,直到停止。
UISpringTimingParameters
遵守UITimingCurveProvider
協議,為遵守UIViewAnimating
協議的對象提供時間曲線,如UIViewPropertyAnimator
。UISpringTimingParameters
一般用于移動屏幕上的視圖,也可用于視圖的其它屬性,以獲取類似的動畫效果。
繼續更新TimingCurvesViewController.m
內代碼,使用UISpringTimingParameters
作為動畫運行時間參數。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 1.在1.5倍durationOfAnimation時間后,觸發動畫。
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.durationOfAnimation*1.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
// 2.動畫使用默認時間曲線函數。
// [self startAnimationsWithDefaultCurves];
// 3.使用UICubicTimingParameters。
// [self startAnimationWithCubicCurves];
// 4.使用UISpringTimingParameters。
[self startAnimationWithSpringCurves];
// 啟動動畫。
[self.redAnimator startAnimation];
[self.blueAnimator startAnimation];
[self.greenAnimator startAnimation];
[self.yellowAnimator startAnimation];
}];
}
// UISpringTimingParameters
- (void)startAnimationWithSpringCurves {
// dampingRatio值分別為0.2 1.0 2.0。
CGFloat underDamped = 0.2;
CGFloat criticalDamped = 1.0;
CGFloat overDamped = 2.0;
self.redAnimator = [self animatorForView:self.redView startFrame:self.redStartFrame dampingRatio:underDamped];
self.blueAnimator = [self animatorForView:self.blueView startFrame:self.blueStartFrame dampingRatio:criticalDamped];
self.greenAnimator = [self animatorForView:self.greenView startFrame: self.greenStartFrame dampingRatio:overDamped];
}
- (UIViewPropertyAnimator *)animatorForView:(UIView *)view startFrame:(CGRect)startFrame dampingRatio:(CGFloat)dampingRatio {
view.frame = startFrame;
CGRect finalRect = [self finalRectWithStartFrame:startFrame];
UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:self.durationOfAnimation dampingRatio:dampingRatio animations:^{
view.frame = finalRect;
}];
return animator;
}
另外,也可以使用initWithMass: stiffness: damping: initialVelocity:
方法,該方法會把阻尼系數(damping)、質量參數(mass)、剛性系數(stiffness)和初始速度(initial velocity)帶入給定公式,以獲取更為真實的效果。
運行demo,如下所示:
UIDynamicAnimator
中的UISnapBehavior
也可以產生彈簧(spring)效果,UISnapBehavior
是移動到指定點point,如下圖所示。想要全面了解UIKitDynamicAnimator
,可以查看一篇文章學會使用UIKit Dynamics。
Demo名稱:PropertyAnimator
源碼地址:https://github.com/pro648/BasicDemos-iOS
參考資料: