iOS動畫-CAAnimation使用詳解

理解了隱式動畫后,顯式動畫就更加通俗易懂了。區(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動畫的進一步封裝。


實現(xiàn)動畫的方式.png

2.核心動畫Core Animation常用類的繼承關系

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

核心動畫類的繼承關系.jpg
動畫類 動畫特性
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屬性應用總結

時間屬性的綜合應用.png

三、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

這五種不同的緩沖效果如下:

動畫緩沖屬性timingFunction.jpg

通過這種方法控制動畫速度,其實是使用不同的變量創(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 陰影角度

附:KeyPath官方參考鏈接

五、基礎動畫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];
}

效果圖如下:

CABasicAnimation.gif

總結創(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];
}

關鍵幀動畫效果如下:

CAKeyframeAnimation_values.gif

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];
}

關鍵幀動畫效果圖如下:

CAKeyframeAnimation_path.gif

七、動畫組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"];
}

動畫組的效果如下:

CAGroupAnimation.gif

八、過渡動畫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.gif

注意:和屬性動畫不同,對指定圖層一次只能使用那一次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];
    }];
 }

自定義過渡動畫的效果如下:

CATransitionn_Custom.gif

注意:-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);

再來看動畫的效果如下:

CABasicAnimation_VirtualProperty.gif

總結transform.rotation相比transfrom做動畫的好處如下:

  1. 可以不通過關鍵幀,只一個步驟就實現(xiàn)旋轉多于180度的動畫;
  2. 可以使用相對值而不是絕對值旋轉,設置byValue而不是toValue;
  3. 可以不用創(chuàng)建CATransform3D,而是使用一個簡單的數(shù)值來指定角度;
  4. 不會和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");
}

測試取消動畫效果圖如下:

CAAnimation_cancel.gif

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內容