理解了隱式動畫后,顯式動畫就更加通俗易懂了。區(qū)別于隱式動畫的特點,顯式動畫就是需要我們明確指定類型、時間等參數(shù)來實現(xiàn)效果的動畫。除此之外,我們也可以創(chuàng)建非線性動畫,比如沿著任意一條曲線運動等;
我們平時最常用的也是顯式動畫,不僅系統(tǒng)為我們的視圖提供了UIViewAnimationWithBlock的動畫封裝,而且我們在熟悉了Core Animation的動畫屬性后也可以很方便的設置顯式動畫;
本篇主要內容:
1.iOS動畫的分類
2.CAMediaTiming協(xié)議
3.CAAnimation基類
4.CAPropertyAnimation基類
5.基礎動畫CABasicAnimation
6.關鍵幀動畫CAKeyframeAnimation
7.動畫組CAGroupAnimation
8.過渡動畫CATransition
9.委托模式下的動畫區(qū)分
10.虛擬屬性及其作用
11.動畫的取消
相關文章:
iOS動畫-CALayer寄宿圖與繪制原理
iOS動畫-CALayer布局屬性詳解
iOS動畫-CALayer隱式動畫原理與特性
iOS動畫-CAAnimation使用詳解
一、動畫的分類
1、實現(xiàn)動畫的方式
如果根據(jù)實現(xiàn)動畫時直接操作對象的類型,我們可以簡單的將動畫分為視圖和圖層兩種;但事實上,無論UIViewAnimaiton動畫還是UIViewAnimaitonWithBlock動畫都只是對UIView的關聯(lián)圖層CALayer動畫的進一步封裝。

2.核心動畫Core Animation常用類的繼承關系
我們在使用Core Animation動畫之前,有必要對核心動畫常見的類和動畫屬性做一個基本了解;從繼承關系的圖示中,我們可以十分清晰的看出這些屬性設置設置因何而來,以及它們各自的聯(lián)系。

| 動畫類 | 動畫特性 |
|---|---|
| CAMediaTiming | 協(xié)議;定義了一段動畫內用于控制時間的屬性的集合 |
| CAAnimation | 抽象類;作為所有動畫類型父類,不可直接使用 |
| CAPropertyAnimation | 抽象類;作為基礎動畫和幀動畫的父類,不可直接使用 |
| CABasicAnimation | 基礎動畫;用于實現(xiàn)單一屬性變化的動畫 |
| CAKeyFrameAnimation | 關鍵幀動畫;用于實現(xiàn)單一屬性連續(xù)變化的動畫 |
| CAAnimaitionGroup | 組動畫;用于實現(xiàn)多屬性同時變化的動畫 |
| CATrasition | 轉場過渡動畫; |
二、CAMediaTiming協(xié)議
CAMediaTiming協(xié)議定義了一段動畫內用于控制時間的屬性的集合,CALayer和CAAnimation都實現(xiàn)了這個協(xié)議,所以時間可以被任意基于一個圖層或者一段動畫的類控制,有關CAMediaTimg協(xié)議具體的屬性如下:
| 屬性 | 參數(shù)類型 | 具體描述 |
|---|---|---|
| beginTime | CFTimeInterval | 動畫開始之前的延遲時間,這里的延遲從動畫添加到可見圖層上那一刻開始測量; (設置動畫beginTime為1,動畫將延時1秒后開始執(zhí)行) |
| duration | CFTimeInterval | 動畫持續(xù)時間; (默認值為0,但是實際動畫默認持續(xù)時間為0.25秒) |
| speed | float | 動畫執(zhí)行的速度; (默認值為0,減少它會減慢動畫的時間,增加它會加快速度) (設置speed為2時,則動畫實際執(zhí)行時間是duration的一半) |
| timeOffset | CFTimeInterval | 動畫時間偏移量; (設置時長3秒動畫的timeOffset為1時,動畫會從1秒位置執(zhí)到最后,再執(zhí)行之前跳過的部分) |
| repeatCount | float | 動畫重復次數(shù);默認值是0,但是實際默認動畫執(zhí)行1次; (設置為INFINITY,則一直執(zhí)行); (duration是2,repeatCount設置為3.5,則完整動畫時長7秒) |
| repeatDuration | CFTimeInterval | 動畫重復的時間,讓動畫重復執(zhí)行一個指定的時間; (設置為INFINITY,一直執(zhí)行) repeatCount和repeatDuration可能會相互沖突,所以你只需要對其中一個指定非零值,對兩個屬性都設置非0值的行為沒有被定義; |
| autoreverses | BOOL | 動畫從初始值執(zhí)行到最終值,是否會反向回到初始值; (設置為YES,動畫完成后將以動畫的形式回到初始位置) |
| fillMode | NSStrinng | 決定當前對象在非動畫時間端段的動畫屬性值,如動畫開始之前和動畫結束之后 |
1.fillMode詳細說明
試想這樣一個問題:在beginTime非0(即動畫未真正執(zhí)行之前),以及removeOnCompletion被設置為NO的動畫結束時,我們會遇到這樣一個問題:被設置動畫的屬性應該是什么值?
一種可能是屬性與動畫沒被添加之前保持一致,還有一種可能是保持動畫開始之前那一幀或者動畫結束那一幀,這就是所謂的填充。
CAMediaTiming的fillMode用來控制填充效果,它是一個NSString類型,有四種常量可供使用:
| fillMode類型 | 參數(shù)類型 | 具體描述 |
|---|---|---|
| kCAFillModeRemoved (default) | NSString | 默認值,動畫開始前和結束后,動畫對圖層都沒有影響,圖層依然保持初始值 |
| kCAFillModeForwards | NSString | 動畫結束后,圖層一直保持動畫后的最終狀態(tài) |
| kCAFillModeBackwards | NSString | 動畫開始前,只要加入動畫就會處于動畫的初始狀態(tài) |
| kCAFillModeBoth | NSString | 綜合了kCAFillModeForwards與kCAFillModeBackwards特性; (動畫加入圖層到真正執(zhí)行動畫的時間段里,圖層保持動畫初始狀態(tài);動畫結束之后保持動畫最終狀態(tài)) |
特別注意:removedOnCompletion需要設置為NO,否則fillMode不起作用;
2.CAMediaTiming屬性應用總結

三、CAAnimation基類
CAAnimation作為所有動畫類型父類,是一個抽象類;我們不能直接使用CAAnimation類,而是使用它的子類;關于它的定義如下:
@interface CAAnimation : NSObject<NSSecureCoding, NSCopying, CAMediaTiming, CAAction>
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
@property(nullable, strong) id <CAAnimationDelegate> delegate;
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
@end
可以看到,CAAnimation動畫基類遵循了CAMediaTiming協(xié)議,而且另外包含了三個常用的動畫屬性;下面是對這三個屬性的總結:
1.動畫緩沖屬性timingFunction
動畫實際上就是在一段時間內隨著某個特定速率執(zhí)行變化的過程,現(xiàn)實中的任何物體都會在運動中經歷加速或者減速的過程,而不是速度驟變;因此,CoreAnimation也內嵌了一系列標準的緩沖函數(shù)來使動畫看起來更平滑自然,這就是我們要說到的動畫緩沖。
timingFunction屬性是CAMediaTimingFunction類的一個對象,用來控制圖層動畫變換的速度;使用它需要調用+functionWithName:的構造方法,下面是可傳入的變量的介紹:
| 變量名 | 具體說明 |
|---|---|
| KCAMediaTimingFuncationLinear | 默認,勻速執(zhí)行動畫 |
| KCAMediaTimingFuncationEaseIn | 先慢慢加速,后突然停止 |
| KCAMediaTimingFuncationEaseOut | 先全速開始,再慢慢減速停止 |
| KCAMediaTimingFuncationEaseInEaseOut | 先慢慢加速,再慢慢減速 |
| KCAMediaTimingFuncationDefault | 效果同KCAMediaTimingFuncationEaseInEaseOut |
這五種不同的緩沖效果如下:

通過這種方法控制動畫速度,其實是使用不同的變量創(chuàng)建了不同的計時函數(shù)。比如KCAMediaTimingFuncationLinear選項創(chuàng)建的是一個線性的計時函數(shù),這也是CAAnimation的timingFunction屬性為空時候的默認函數(shù)。
注意:KCAMediaTimingFuncationDefault相比KCAMediaTimingFuncationEaseInEaseOut的加速和減速過程稍微有些慢,兩者區(qū)別很難察覺;可能蘋果也覺得它更適合用于隱式動畫,就作為了隱式動畫的默認效果;但是創(chuàng)建顯式的CAAnimation時,KCAMediaTimingFuncationLinear才是默認效果而非KCAMediaTimingFuncationDefault;
UIKit動畫其實也同樣支持這些緩沖效果的使用,在我們使用UIViewAnimationBlock實現(xiàn)動畫的時候,可以給options參數(shù)提供了如下的常量來修改緩沖效果:
| 變量名 | 具體說明 |
|---|---|
| UIViewAnimationOptionCurveLinear | 默認,勻速執(zhí)行動畫 |
| UIViewAnimationOptionCurveEaseIn | 先慢慢加速,后突然停止 |
| UIViewAnimationOptionCurveEaseOut | 先全速開始,再慢慢減速停止 |
| UIViewAnimationOptionCurveEaseInOut | 先慢慢加速,再慢慢減速 |
2.動畫代理屬性delegate
/* Delegate methods for CAAnimation. */
@protocol CAAnimationDelegate <NSObject>
@optional
//動畫開始時調用
- (void)animationDidStart:(CAAnimation *)anim;
//動畫結束時調用
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end
3.removedOnCompletion
removedOnCompletion屬性默認為YES,表示動畫完成后就會從圖層上移除,圖層也會恢復到動畫執(zhí)行前的狀態(tài);當其修改為NO時,那么圖層將會保持動畫結束后的狀態(tài),此時的fillMode屬性也將生效;
另外,removedOnCompletion設置為NO時,直到我們手動移除動畫,否則動畫將不會自動釋放;所以通常我們此時會給動畫添加一個非空的鍵,這樣可以在不需要動畫的時候把它從圖層上移除;
四、CAPropertyAnimation基類
CAPropertyAnimation是一個抽象類,不能直接用于實現(xiàn)CALayer動畫操作,但是它的類定義中增加用于設置CALayer可被實現(xiàn)動畫的屬性keyPath,總結這些屬性如下:
| 屬性 | 解讀 |
|---|---|
| transform.rotation | 默認圍繞z軸旋轉,相當于transform.rotation.z |
| transform.rotation.x transform.rotation.y transform.rotation.z |
分別圍繞x軸、y軸、z軸旋轉; |
| transform.scale | 在所有方向上進行縮放 |
| transform.scale.x transform.scale.y transform.scale.z |
分別在x軸、y軸、z軸方向上縮放; |
| transform.translation | 平移到指定坐標點 |
| transform.translation.x transform.translation.y transform.translation.z |
分別在x軸、y軸、z軸方向上平移; |
| zPosition | z軸位置 |
| opacity | 透明度 |
| backgroundColor | 背景顏色 |
| cornerRadius | 圓角大小 |
| borderWidth | 邊框寬度 |
| bounds | 圖層大小 |
| contents | 寄宿圖內容 |
| contentsRect | 可視內容 |
| position | 圖層位置,類似transform.translation |
| shadowColor | 陰影顏色 |
| shadowOffset | 陰影偏移 |
| shadowOpacity | 陰影透明度 |
| shadowRadius | 陰影角度 |
五、基礎動畫CABasicAnimation
CABasicAnimation即基礎動畫,在指定可動畫屬性后,動畫會按照預定的參數(shù)持續(xù)一定時間由初始值變換為終點值。其實,CABasicAnimation就相當于只有開始和結束兩個幀的特殊關鍵幀動畫(后續(xù)會詳解);
1.屬性說明
| 屬性 | 屬性說明 |
|---|---|
| fromValue | 起始值 |
| toValue | 結束值 |
| byValue | keyPath屬性的變化值 |
2.動畫演示
下面的示例使用CABasicAnimation實現(xiàn)了修改顏色圖層colorLayer的背景色為隨機顏色的動畫,具體的代碼如下:
@interface TestBacicAnimation1VC ()<CAAnimationDelegate>
@property (nonatomic,strong) CALayer *colorLayer;
@end
@implementation TestBacicAnimation1VC
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建顯示顏色的圖層,添加于視圖控制器的View上
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(50, 50, 100, 100);
colorLayer.backgroundColor = [UIColor redColor].CGColor;
self.colorLayer = colorLayer;
[self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
//步驟1:創(chuàng)建動畫
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
//步驟2:設定動畫屬性
animation.autoreverses = NO;
animation.duration = 0.25;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.delegate = self;
UIColor *randomColor = [UIColor randomColor]; //自定義獲取隨機色的方法
animation.toValue = (__bridge id _Nullable)(randomColor.CGColor);
//步驟3:添加動畫到圖層
[self.colorLayer addAnimation:animation forKey:@"keyPath_backgroundColor"];
}
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{
//禁用隱式動畫
[CATransaction begin];
[CATransaction setDisableActions:true];
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
效果圖如下:

總結創(chuàng)建動畫的兩種方式如下:
//方法1:實例化同時指定動畫類型
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
//方法2:先實例化,再指定動畫類型
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
3.關閉隱式動畫
對獨立圖層(即非UIView的關聯(lián)圖層,類似上述例子中的colorLayer)做更新屬性的顯式動畫,我們需要設置一個事務來禁用圖層行為,否則動畫會發(fā)生兩次,一次是因為顯式的CABasicAnimation,另一次是因為隱式動畫,從而導致我們看到的動畫異常。
六、關鍵幀動畫CAKeyframeAnimation
CACAKeyfameAnimation是CAPropertyAnimation的另一個子類,它和和CABasicAnimation一樣都只能作用于圖層對象的單一屬性;它們的區(qū)別在于:CACAKeyfameAnimation不限制于設置一個起始值和結束值,而是可以根據(jù)一連串的值來做動畫。其實,CABasicAnimation可看做是只有2個關鍵幀的CAKeyframeAnimation。
1.關鍵幀動畫常用屬性總結
關鍵幀動畫相對于基礎動畫的具有一些獨特的屬性,我們現(xiàn)將其總結如下:
| 屬性 | 具體描述 |
|---|---|
| values | 用于提供關鍵幀數(shù)據(jù)的數(shù)組,數(shù)組中每一個值都對應一個關鍵幀屬性值; 數(shù)組中的數(shù)據(jù)類型根據(jù)動畫類型(KeyPath)而不同; 當使用path的時候,values的值將會被自動忽略; |
| path | 用于提供關鍵幀數(shù)據(jù)的路徑; path與values屬性作用相同,但是兩者互斥,同時指定values和path,path會覆蓋values的效果; |
| keyTimes | ktyTimes與Values中的值具有一一對應的關系,用于指定關鍵幀在動畫的時間點,取值范圍是[0,1]; 若沒有設置keyTimes,則每個關鍵幀的時間是平分動畫總時長(duration); |
| timingFunctions | 用于指定每個關鍵幀之間的動畫緩沖效果,這類似于物體運動的加速度; 注意:存在幾個子路徑就應該在此數(shù)組中傳入幾個元素; |
| calculationMode | 該屬性決定了物體在每個子路徑下是跳著走還是勻速走,跟timeFunctions屬性有點類似; |
| rotationMode | 設置幀動畫是否需要按照路徑切線的方向運動; |
2.實現(xiàn)幀動畫:使用values
從關鍵幀動畫的屬性可以看出,我們可以總結出關鍵幀動畫的實現(xiàn)方式實際分為兩種:
1.通過values設置關鍵幀屬性值數(shù)組;
2.通過path設置關鍵幀路徑,而且此種方式的優(yōu)先級較高;
這里首先測試第一種方式,實現(xiàn)這樣的關鍵幀動畫:創(chuàng)建一個紫色滑塊在四個坐標點之間滑動;具體的代碼實現(xiàn)如下:
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建測試幀動畫的紫色圖層
UIView *purpleView = [UIView new];
purpleView.frame = CGRectMake(0, 0, 50, 50);
purpleView.center = CGPointMake(50, 100);
purpleView.backgroundColor = [UIColor purpleColor];
[self.view addSubview:purpleView];
//步驟1:創(chuàng)建動畫
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
//步驟2:設置動畫關鍵幀數(shù)據(jù)
NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(50, 100)];
NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(kDeviceWidth -50, 100)];
NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(kDeviceWidth -50, kDeviceWidth- 100)];
NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(50, kDeviceWidth -100)];
NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(50, 100)];
animation.values = @[value1,value2,value3,value4,value5];
//步驟3:設定動畫屬性
animation.repeatCount = MAXFLOAT; //重復執(zhí)行
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.duration = 4;
//animation.keyTimes = @[@(0), @(1 / 10.0), @(5 / 10.0), @(9 / 10.0), @(1) ];
animation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[purpleView.layer addAnimation:animation forKey:nil];
}
關鍵幀動畫效果如下:

3.實現(xiàn)關鍵幀動畫:使用path
現(xiàn)在,我們測試CAKeyframeAnimation使用path實現(xiàn)這樣一個動畫:一架飛機沿著一個簡單的曲線運動飛行;具體的操作包括以下幾個步驟:
1.使用UIKit提供的UIBezierPath類創(chuàng)建貝塞爾曲線,作為飛機飛行的路線軌跡;
2.使用CAShapeLayer在屏幕上繪制曲線(此步驟對于動畫不是必須的,只是為了動畫看起來更直觀);
3.創(chuàng)建用于顯示飛機的視圖,將其設置在貝塞爾曲線的初始位置;
4.創(chuàng)建并執(zhí)行關鍵幀動畫,實現(xiàn)飛機飛行的曲線動畫;
- (void)viewDidLoad {
[super viewDidLoad];
//1.創(chuàng)建三次貝塞爾曲線(一種使用起始點,結束點和另外兩個控制點定義的曲線);
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(50, 200)];
[bezierPath addCurveToPoint:CGPointMake(kDeviceWidth - 50, 200) controlPoint1:CGPointMake(150, 50) controlPoint2:CGPointMake(kDeviceWidth - 150, 250)];
//2.繪制飛行路線
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.view.layer addSublayer:pathLayer];
//3.創(chuàng)建顯示飛機的視圖
UIImageView *airPlaneImgView = [[UIImageView alloc] init];
airPlaneImgView.frame = CGRectMake(0, 0, 50, 50);
airPlaneImgView.center = CGPointMake(50, 200);
airPlaneImgView.image = [UIImage imageNamed:@"airplane"];
[self.view addSubview:airPlaneImgView];
//4.設置關鍵幀動畫
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 5.0;
animation.path = bezierPath.CGPath;
animation.rotationMode = kCAAnimationRotateAuto; //設置根據(jù)曲線的切線自動旋轉,讓動畫更加真實
[airPlaneImgView.layer addAnimation:animation forKey:nil];
}
關鍵幀動畫效果圖如下:

七、動畫組CAGroupAnimation
CAGroupAnimation顧名思義,就是可以將不同的動畫效果組合起來,CABasicAnimation和CAKeyframeAnimation都僅僅作用于單一的屬性,而CAAnimationGrop可以設置其animations數(shù)組的屬性來組合別的動畫,從而達到混合多種動畫效果的目的;
下面演示一個動畫組的示例:組合基礎動畫和關鍵幀動畫,實現(xiàn)一個滑塊在沿path運動過程修改其顏色,具體的測試代碼如下:
@interface TestAnimationGroupVC ()
@property (nonatomic,strong) UIView *colorView;
@property (nonatomic,strong) UIBezierPath *bezierPath;
@end
@implementation TestAnimationGroupVC
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建顯示顏色的圖層
self.colorView = [UIView new];
self.colorView.frame = CGRectMake(0, 0, 60, 60);
self.colorView.center = CGPointMake(50, 200);
self.colorView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:self.colorView];
//創(chuàng)建貝塞爾曲線,即幀動畫運動軌跡
self.bezierPath = [[UIBezierPath alloc] init];
[self.bezierPath moveToPoint:CGPointMake(50, 200)];
[self.bezierPath addCurveToPoint:CGPointMake(kDeviceWidth - 50, 200) controlPoint1:CGPointMake(150, 50) controlPoint2:CGPointMake(kDeviceWidth - 150, 250)];
//繪制繪制path,便于觀察動畫;
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = self.bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.view.layer addSublayer:pathLayer];
}
- (IBAction)startAnimation:(UIButton *)sender{
//移除可能未執(zhí)行完的動畫,防止多重動畫導致異常
[self.colorView.layer removeAnimationForKey:@"groupAnimation"];
//1.創(chuàng)建基礎動畫:修改背景色為紫色
CABasicAnimation *basicAnimation = [CABasicAnimation animation];
basicAnimation.keyPath = @"backgroundColor";
basicAnimation.toValue = (__bridge id _Nullable)([UIColor purpleColor].CGColor);
//2.創(chuàng)建關鍵幀動畫
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animation];
keyFrameAnimation.keyPath = @"position";
keyFrameAnimation.path = self.bezierPath.CGPath;
keyFrameAnimation.rotationMode = kCAAnimationRotateAuto;
//3.創(chuàng)建組動畫:組合基礎動畫和關鍵幀動畫
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[basicAnimation, keyFrameAnimation];
groupAnimation.duration = 4.0;
[self.colorView.layer addAnimation:groupAnimation forKey:@"groupAnimation"];
}
動畫組的效果如下:

八、過渡動畫CATransition
1.過渡動畫簡介
屬性動畫只能對圖層的可動畫屬性起作用,而過渡動畫可以改變非動畫屬性(比如交換一段文本和圖片),或者從層級關系中添加或者移除圖層;于是就有了過渡的概念;
過渡動畫使用CATransition來實現(xiàn),它同樣是CAAnimation的子類;它并不像屬性動畫那樣在平滑的兩個值之間做動畫,而是影響到整個圖層的變化。過渡動畫首先展示之前的圖層外觀,然后通過一個交換過渡到新的外觀。
過渡動畫通常用于刪除子控件、添加子控件、切換兩個子控件等。
2.過渡動畫屬性介紹
過渡動畫有type和subtype兩個關鍵屬性,type用于指定動畫類型,subtype用于指定動畫移動的方向;
type屬性:
type屬性是一個NSString類型,用于控制整體動畫效果類型,具體的可選類型如下:
| type值 | 動畫效果 | 對應常量 | 是否支持方向 |
|---|---|---|---|
| fade | 默認效果,漸變 | kCATransitionFade | 否 |
| moveIn | 覆蓋 | kCATransitionMoveIn | 是 |
| Push | 退出 | kCATransitionPush | 是 |
| Reveal | 揭開 | kCATransitionReveal | 是 |
| cube | 立方體 | 無(私有類型) | 是 |
| suckEffect | 收縮 | 無(私有類型) | 否 |
| oglFlip | 翻轉 | 無(私有類型) | 是 |
| rippleEffect | 水波動畫 | 無(私有類型) | 否 |
| pageCurl | 頁面揭開 | 無(私有類型) | 只支持左右方向 |
| vpageUnCurl | 放下頁面 | 無(私有類型) | 只支持左右方向 |
| cameraIrisHollowOpen | 鏡頭打開 | 無(私有類型) | 否 |
| cameraIrisHollowClose | 鏡頭關閉 | 無(私有類型) | 否 |
目前為止,我們只能使用type的前四種公開屬性,但是我們可以通過一些別的方法來自定義過渡效果(后續(xù)介紹);
subtype屬性:
subtype屬性也是一個NSString類型,用于控制動畫方向,具體的可選類型如下:
| Subtype類型 | 具體描述 |
|---|---|
| kCATransitionFromRight | 從右向左 |
| kCATransitionFromLeft | 從左向右 |
| kCATransitionFromTop | 從上向下 |
| kCATransitionFromBottom | 從下向上 |
3.過渡動畫的使用
現(xiàn)在設想這樣的一個需求:修改UIImageView的image屬性,實現(xiàn)淡入淡出的平滑動畫的效果;此時我們需要使用CATransition來對非動畫屬性做動畫,具體的關鍵代碼如下:
@interface TestTransition1VC ()
@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,strong) NSArray *images;
@property (nonatomic, copy) NSString *type;
@property (nonatomic, copy) NSString *subtype;
@end
@implementation TestTransition1VC
- (void)viewDidLoad {
[super viewDidLoad];
self.images = @[[UIImage imageNamed:@"tree_spring"],
[UIImage imageNamed:@"tree_summer"],
[UIImage imageNamed:@"tree_autumn"],
[UIImage imageNamed:@"tree_winter"]];
self.type = kCATransitionFade;
self.subtype = kCATransitionFromRight;
}
- (void)perforomTransitionAnimation{
CATransition *transition = [[CATransition alloc] init];
transition.type = _type;
transition.subtype = _subtype;
transition.duration = 0.5;
[self.imageView.layer addAnimation:transition forKey:nil];
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % self.images.count;
self.imageView.image = self.images[index];
}
過渡動畫的效果如下:

注意:和屬性動畫不同,對指定圖層一次只能使用那一次CATransition,因此無論對動畫的鍵設置為什么值,過渡動畫都會對它的鍵設置為”transition”,也就是常量KCATransition.
4.隱式過渡
CATransition可以對圖層任何變化平滑過渡,這使得它成為那些不好做動畫的屬性圖層行為的理想之選。所以,蘋果將CATransition作為設置CALayer的contents屬性時的默認行為,對圖層contents圖片做的改動都會自動附上淡入淡出的效果,這也就解釋了隱式動畫的原理;
但注意:
1.對于視圖關聯(lián)的圖層,過渡動畫的默認效果是禁用的;
2.我們不能錯誤的理解CATransition只可以改變非動畫屬性,其實它也可以對類似backgroundColor的屬性做過渡效果動畫;
5.自定義過渡動畫
過渡動畫的過程就是對原始圖層外觀截圖,然后添加一段動畫,平滑過渡到圖層改變之后的那個截圖效果。如果我們知道如何對圖層截圖,我們就可以使用屬性動畫來自定義CATransition動畫了。
CALayer有一個-renderInContenxt:方法,通過它可以將圖層繪制到Core Graphics的上下文中捕獲當前內容的圖片;所以現(xiàn)在我們嘗試這樣的實現(xiàn):對當前視圖控制器View進行截圖,然后在改變其背景色的時候對截圖快速旋轉并且淡出,以達到一種過渡的效果;具體的代碼示例如下:
- (void)performAnimation{
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
coverView.frame = self.view.bounds;
[self.view addSubview:coverView];
//使用自定義方法得到隨機顏色(切換后的顏色)
UIColor *randomColor = [UIColor randomColor];
self.view.backgroundColor = randomColor;
//使用UIView動畫方法來代替屬性動畫(為了簡化代碼步驟)
[UIView animateWithDuration:1 animations:^{
CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
transform = CGAffineTransformRotate(transform, M_PI_2);
coverView.transform = transform;
coverView.alpha = 0.0;
} completion:^(BOOL finished) {
[coverView removeFromSuperview];
}];
}
自定義過渡動畫的效果如下:

注意:-renderInContext:捕獲了圖層的圖片和子圖層,但是不能對子圖層正確的處理變換效果,而且對視頻和OpenGL內容也不起作用。但是使用CATransition,或者使用私有的截屏方式就沒有這個限制了。
九、委托模式下的動畫區(qū)分
對于CAAnimation而言,使用委托模式而不是一個完成塊會帶來一個問題,那就是設置多個動畫時,無法在回調方法中區(qū)分。通常視圖控制器本身會作為一個委托,但所有動畫都會調用同一個回調方法,所以我們需要判斷到底是哪個圖層的動畫調用;
首先,動畫本身會作為一個參數(shù)傳入委托的方法,也許你會認為可以在控制器中把動畫存儲為一個屬性,然后在回調用比較,但實際上并不起作用,因為委托傳入的動畫參數(shù)是原始值的一個深拷貝,從而不是同一個值。最后,這里提供兩種思路來解決這個問題:
思路1:唯一key參數(shù)
當使用-addAnimation:forkey:添加動畫到圖層時,對每個動畫都關聯(lián)一個唯一的鍵,這樣就可以對每個圖層循環(huán)所有鍵,然后調用animationForKey:來對比結果;
思路2:KVC(鍵-值-編碼)協(xié)議
像所有NSObject子類一樣,CAAnimation也遵循了KVC協(xié)議,就像一個NSDictionary一樣允許我們隨意設置鍵值對;于是我們可以使用setValue:forKey:和-valueForKey:來存取屬性,通過為對象創(chuàng)建一個鍵值對來判斷區(qū)分動畫;
驗證上述兩種思路的具體的代碼使用如下:
@interface TestBacicAnimation2VC ()<CAAnimationDelegate>
@property (nonatomic,strong) UIView *colorView;
@property (nonatomic,strong) UIView *opacityView;
@end
@implementation TestBacicAnimation2VC
#pragma mark - Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建顯示顏色的圖層
UIView *colorView = [UIView new];
colorView.frame = CGRectMake(50, 50, 100, 100);
colorView.backgroundColor = [UIColor redColor];
self.colorView = colorView;
[self.view addSubview:self.colorView];
//創(chuàng)建透明度視圖
UIView *opacityView = [UIView new];
opacityView.frame = CGRectMake(50, 200, 100, 100);
opacityView.backgroundColor = [UIColor blueColor];
self.opacityView = opacityView;
[self.view addSubview:self.opacityView];
}
- (IBAction)startAnimation:(UIButton *)sender{
//背景色顏色動畫
CABasicAnimation *animation1 = [CABasicAnimation animation];
animation1.keyPath = @"backgroundColor";
animation1.autoreverses = NO;
animation1.duration = 1;
animation1.repeatCount = 1;
animation1.removedOnCompletion = NO;
animation1.fillMode = kCAFillModeForwards;
animation1.delegate = self;
UIColor *randomColor = [UIColor randomColor]; //自定義獲取隨機色的方法
animation1.toValue = (__bridge id _Nullable)(randomColor.CGColor);
[animation1 setValue:@"animation_background" forKey:@"AnimationKey"];
[self.colorView.layer addAnimation:animation1 forKey:@"key_backgroundColor"];
//透明度動畫
CABasicAnimation *animation2 = [CABasicAnimation animation];
animation2.keyPath = @"opacity";
animation2.autoreverses = NO;
animation2.duration = 5;
animation2.repeatCount = 1;
animation2.removedOnCompletion = NO;
animation2.fillMode = kCAFillModeForwards;
animation2.delegate = self;
animation2.fromValue = @(1);
animation2.toValue = @(0);
[animation2 setValue:@"animation_opacity" forKey:@"AnimationKey"];
[self.opacityView.layer addAnimation:animation2 forKey:@"key_opacity"];
}
//動畫結束的代理:區(qū)分動畫
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
//方法1:唯一key參數(shù)
if([[self.colorView.layer animationForKey:@"key_backgroundColor"] isEqual:anim]){
}
if([[self.opacityView.layer animationForKey:@"key_opacity"] isEqual:anim]){
}
//方法2:KVC
NSString *animationValue = [anim valueForKey:@"AnimationKey"];
NSLog(@"animationValue:%@",animationValue);
if([animationValue isEqualToString:@"animation_background"]){
}else if([animationValue isEqualToString:@"animation_opacity"]){
}
}
注意:使用唯一key參數(shù)這種方法,必須設置removeOnCompletion為NO,否則通過animaitonForKey:獲取的CAAnimation對象為空對象無法進行比較。
十、虛擬屬性
屬性動畫CAPropertyAnimation的keyPath實際上針對的是關鍵路徑而不是一個鍵,這就意味著屬性動畫作用的對象可以子屬性(即屬性的屬性)甚至虛擬屬性;
那么什么是虛擬屬性呢?舉個例子來講,CATransform3D實際上是一個結構體而非一個對象,所以它并不符合KVC相關屬性,但是我們卻可以使用transform.rotation來實現(xiàn)動畫;這其實就是因為transform.rotation是一個CALayer可用于處理動畫變換的虛擬屬性;
1.虛擬屬性的作用
為了理解虛擬屬性的用處,我們現(xiàn)在考慮這樣一個動畫:對一個物體實現(xiàn)旋轉動畫,由于CALayer并沒有顯式的給提供角度或者方向之類的屬性,所以我們自然想到使用transform屬性來實現(xiàn)動畫,測試代碼具體如下:
@interface TestBacicAnimation3VC ()
@property(nonatomic, strong) UILabel *txtLabel;
@end
@implementation TestBacicAnimation3VC
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建測試虛擬屬性的Label
_txtLabel = [UILabel new];
_txtLabel.frame = CGRectMake(50, 300, kDeviceWidth -100 , 50);
_txtLabel.backgroundColor = [UIColor purpleColor];
_txtLabel.font = [UIFont boldSystemFontOfSize:15];
_txtLabel.textAlignment = NSTextAlignmentCenter;
_txtLabel.text = @"測試虛擬屬性";
[self.view addSubview:_txtLabel];
}
- (IBAction)startAnimation:(UIButton *)sender{
//步驟1:創(chuàng)建動畫
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform”; //代碼1
//步驟2:設定動畫屬性
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.duration = 1;
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI * 2, 0, 0, 1)]; //代碼2
[_txtLabel.layer addAnimation:animation forKey:nil];
}
在此例中,我們把旋轉角度從M_PI(180度)調整到M_PI*2(360度),對比兩次動畫會發(fā)現(xiàn),txtLabel完全看不到旋轉的動畫效果;這是因為CATransform3D矩陣做了360度旋轉其實適合0度是一樣的,所以最后的值根本就沒變;
這里就需要用到上述說到的虛擬屬性了,為了旋轉圖層,我們可以針對于transform.rotation關鍵路徑應用動畫,而不是transform本身;現(xiàn)在將對上述代碼進行修改如下:
//animation.keyPath = @"transform"; //代碼1
animation.keyPath = @"transform.rotation”;
//animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI * 2, 0, 0, 1)]; //代碼2
animation.byValue = @(M_PI * 2);
再來看動畫的效果如下:

總結transform.rotation相比transfrom做動畫的好處如下:
- 可以不通過關鍵幀,只一個步驟就實現(xiàn)旋轉多于180度的動畫;
- 可以使用相對值而不是絕對值旋轉,設置byValue而不是toValue;
- 可以不用創(chuàng)建CATransform3D,而是使用一個簡單的數(shù)值來指定角度;
- 不會和transform.position或者transfrom.scale沖突(同樣是使用關鍵路徑來做獨立的動畫屬性);
2.虛擬屬性原理
我們已經說過CATransform3D是一個結構體而非一個對象,所以transfrom.rotation其實是不存在的,我們不可以直接設置transform.rotation或者transform.scale;
實際上,Core Animation是自動通過CAValueFunction計算的值來更新transform屬性的,CAValueFunction將我們賦值虛擬屬性transfom.rotation的浮點值轉換成了真正能用于擺放圖層的CATransform3D矩陣值;我們也可以通過設置CAPropertyAnimation的valuefunction屬性來改變,這樣我們自定義函數(shù)就會覆蓋默認函數(shù)。
CAValueFuncation對于那些不能簡單相加的屬性(例如變換矩陣)做動畫十分有用,但是此方法的實現(xiàn)細節(jié)是私有的,所以,目前我們并不能通過繼承來自定義此方法;我們可以通過使用蘋果已經提供的常量來改善動畫(目前都是和變換矩陣的虛擬屬性相關,所以沒太多的應用場景了,因為這些屬性都有了默認的實現(xiàn)方式)。
十一、在動畫過程中取消動畫
在使用動畫的過程中,我們可能需要適時的移除不要的動畫,否則就可能造成內存的泄漏問題;從圖層中取消動畫的方法有以下兩種方式:
//方法1:取消指定動畫
/* Remove any animation attached to the layer for 'key'. */
- (void)removeAnimationForKey:(NSString *)key;
//方法2:移除所有動畫
/* Remove all animations attached to the layer. */
- (void)removeAllAnimations;
關于移除動畫的幾點說明:
1.動畫一旦被移除,圖層的外觀就立刻更新到當前的模型圖層的值;
2.動畫通常默認結束之后被自動移除,除非設置了removeCompletion為NO;
3.動畫若設置為結束之后不自動移除,那么我們在不需要的時候需手動移除,否則它會一直在內存中,直到圖層被銷毀;
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建測試停止動畫的Label
_txtLabel = [UILabel new];
_txtLabel.frame = CGRectMake(50, 200, kDeviceWidth -100 , 50);
_txtLabel.backgroundColor = [UIColor purpleColor];
_txtLabel.font = [UIFont boldSystemFontOfSize:18];
_txtLabel.textAlignment = NSTextAlignmentCenter;
_txtLabel.textColor = [UIColor whiteColor];
_txtLabel.text = @"測試停止動畫的Label";
[self.view addSubview:_txtLabel];
//添加開始動畫的按鈕
[self.view addSubview:self.button];
[self.button mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self.view).offset(-50);
make.leading.equalTo(self.view).offset(60);
make.trailing.equalTo(self.view).offset(-60);
make.height.mas_equalTo(50);
}];
}
- (void)onBtnClick:(UIButton *)btn {
btn.selected = !btn.selected;
if (btn.selected) {
//停止動畫
[self.txtLabel.layer removeAnimationForKey:@"Animation_transform_rotation"];
[self.button setTitle:@"開始動畫" forState:UIControlStateNormal];
}else{
//開始動畫
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.delegate = self;
animation.duration = 5;
animation.byValue = @(M_PI * 2);
[self.txtLabel.layer addAnimation:animation forKey:@"Animation_transform_rotation"];
[self.button setTitle:@"停止動畫" forState:UIControlStateNormal];
}
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"The animation stopped (finished:%@)",flag? @"YES" : @"NO");
}
測試取消動畫效果圖如下:

代碼分析:
-animationDidStop:finished:方法中的flag參數(shù)表明了動畫是自然結束還是被打斷的;此例中通過停止按鈕來終止動畫會打印NO,自然完成動畫時打印YES;