聲明
該篇文章的內(nèi)容參考自 iOS核心動畫高級技巧 一文距辆,非常感謝其作者和中文版的作者芜果,讓我能夠相對系統(tǒng)的學(xué)習(xí) CoreAnimation 的知識换薄,我受益匪淺,再次感謝翔试。
如果有興趣的小伙伴可以訪問其網(wǎng)站轻要,詳細(xì)的,完整的學(xué)習(xí) CoreAnimation垦缅。
CAAnimation 篇
CAAnimation 是一個抽象動畫類冲泥。 遵循著 CAMediaTiming 和 CAAciotn 兩個協(xié)議。 要為 Core Animation 圖層或 Scene Kit 對象設(shè)置動畫,請創(chuàng)建其子類 CABasicAnimation凡恍,CAKeyframeAnimation志秃,CAAnimationGroup 或 CATransition 的實例。Core Animation 可以用在 Mac OS X 和 iOS 平臺嚼酝。Core Animation 的動畫執(zhí)行過程都是在后臺操作的浮还,不會阻塞主線程。
隱式動畫
當(dāng)你改變 CALayer 的一個可做動畫的屬性闽巩,它并不會立刻在屏幕上呈現(xiàn)出來钧舌,而是從先前的值平滑過渡到新值。典型的例子就是改變圖層的背景填充色涎跨。
示例:
假如我們現(xiàn)在有一個圖層洼冻,那我們在點擊屏幕時嘗試去改變此圖層的背景填充色。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 生成隨機顏色
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
我們可以看到隅很,但我們點擊屏幕以改變圖層的背景時撞牢,視圖從舊的背景逐漸地過度到了新值。在這過程中叔营,我們沒有做其他額外的操作屋彪,這種自行完成的平滑過渡動畫就是隱式動畫。
事務(wù)
那么這一過程時如何完成的呢审编?實際上動畫是由當(dāng)前事務(wù)來完成的撼班,事務(wù)是什么?事務(wù)是 Core Animation 用來包含一系列屬性動畫集合的機制垒酬,你可以設(shè)置動畫的執(zhí)行時間等砰嘁,這些動畫的圖層屬性新值的設(shè)置都不會立刻發(fā)生變化,而是當(dāng)事務(wù)提交時由 run loop 自動開始勘究。
事務(wù)是通過 CATransaction 類來管理矮湘。該類沒有屬性或者實例方法,因此你不能創(chuàng)建它口糕,但是你可以通過 +begin
和 commit
來將當(dāng)前屬性設(shè)置分別進行入棧和出棧操作昌跌。
任何可以做動畫的圖層屬性都會添加到棧頂?shù)氖聞?wù)宝剖,你可以通過 +setAnimationDuration:
方法來設(shè)置當(dāng)前事務(wù)的動畫時間,如果不進行設(shè)置,默認(rèn)的時間是0.25s包竹。
我們現(xiàn)在使用事物來完成上一個例子中的動畫,并將動畫時間延長镐侯。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 開始一個動畫事務(wù)
[CATransaction begin];
// 設(shè)置動畫的執(zhí)行時間
[CATransaction setAnimationDuration:1.0];
// 生成顏色搀继,作為動畫的變化新值
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
// 提交動畫事務(wù)
[CATransaction commit];
}
我們可以看到圖層的動畫效果依舊沒變,但是漸變的時間明顯變長了很多棠绘。從代碼上看件相,我們僅僅將圖層需要改變的屬性加到 +begin
和 commit
之間再扭,并為此事務(wù)設(shè)置了一個時間。
如果你使用過 UIView 的動畫夜矗,那么應(yīng)該使用過 +beginAnimations:context:
和 +commitAnimations
泛范,實際上這兩個都是對 CATransaction
的封裝,其所做動畫都是由 CATransaction
完成的紊撕。
完成回調(diào)
CATranscation
的 API 除了提供設(shè)置動畫時間 +setAnimationDuration:
還提供了動畫完成的回調(diào)方法:+ setCompletionBlock:
罢荡。你可以在該方法中接著完成一些事情。
修改一下代碼:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 開始一個動畫事務(wù)
[CATransaction begin];
// 設(shè)置動畫的執(zhí)行時間
[CATransaction setAnimationDuration:1.0];
// 生成顏色逛揩,作為動畫的變化新值
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
// 提交動畫事務(wù)
[CATransaction commit];
// 動畫完成回調(diào)柠傍,可以寫在 commit 后面
[CATransaction setCompletionBlock:^{
self.colorLayer.affineTransform = CGAffineTransformRotate(self.colorLayer.affineTransform, M_PI_4);
}];
}
圖層動畫的過程
當(dāng)我們給對 CALayer 的屬性設(shè)新值時,圖層經(jīng)過以下幾個過程來檢測應(yīng)該如何呈現(xiàn)新值辩稽。
- 圖層首先檢測它是否有委托者惧笛,并且是否實現(xiàn)了協(xié)議
CALayerDelegate
中的方法-actionForLayer:forKey:
,如果有逞泄,直接調(diào)用并返回結(jié)果患整。 - 如果沒有委托者,或者委托沒有實現(xiàn)上述方法喷众,圖層會檢查屬性
actions
字典各谚,試圖找到對應(yīng)的屬性名。 - 如果依舊沒有到千,圖層還是檢查屬性
style
字典昌渤,再次嘗試搜索對應(yīng)的屬性名。 - 最后憔四,如果都未能找到膀息,那么圖層直接會調(diào)用默認(rèn)的行為
defaultActionForKey:
方法來展現(xiàn)對應(yīng)屬性的新值。
那么了赵,既然我們知道了圖層的行為過程潜支,我們是否可以以此做些什么?實際上柿汛,我們可以參與圖層的行為過程來改變隱式動畫的行為冗酿。
我們首先通過圖層的委托代理完成新的動畫過程。
@interface ViewController ()< CALayerDelegate >
@property (strong, nonatomic)CALayer* colorLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
// 設(shè)置圖層的委托代理
self.colorLayer.delegate = self;
}
// 完成圖層行為協(xié)議
-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
// 設(shè)置新的動畫
CATransition *transition = [CATransition animation];
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromLeft;
return transition;
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 生成隨機顏色.
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
我們也可以通過 actions
字典來完成:
@interface ViewController ()
<
CALayerDelegate
>
@property (strong, nonatomic)CALayer* colorLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
// 設(shè)置 actions 字典
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transition.duration = 1.0; // 動畫時間設(shè)置稍長
self.colorLayer.actions = @{@"backgroundColor": transition};
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 生成隨機顏色
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
顯式動畫
和隱式動畫相對的络断,顯式動畫一般是開發(fā)者們主動去實現(xiàn)的動畫效果裁替,完成圖層從舊狀態(tài)到新狀態(tài)到過渡切換。和隱式動畫不不同貌笨,顯式動畫需要開發(fā)者關(guān)心動畫從產(chǎn)生到消失的每一個細(xì)節(jié)胯究,如變化的狀態(tài)、執(zhí)行的時長躁绸、動畫的次數(shù)等等,相比系統(tǒng)提供的簡單的過渡動畫效果,顯式動畫可以完成圖層的各種各樣的酷炫效果净刮。
屬性動畫
顧名思義剥哑,屬性動畫(CAPropertyAnimation)類的是針對圖層的一些可作動畫的屬性而言的,該類不能直接拿來使用淹父,開發(fā)中通常使用其子類(這一點類似手勢)株婴,如:CABasicAnimation 經(jīng)典動畫、CAKeyframeAnimation 關(guān)鍵幀動畫暑认、CASpringAnimation 彈性動畫困介,基礎(chǔ)動畫的子類。
- CABasicAnimation
CABasicAnimation 動畫蘸际,需要我們?yōu)槠涮峁﹥蓚€狀態(tài)值座哩,一個是初始狀態(tài)值,一個是終止?fàn)顟B(tài)值粮彤。一般來說根穷,初始值都是圖層最初的狀態(tài),當(dāng)然导坟,你也可以指定從初始狀態(tài)到非終止?fàn)顟B(tài)的之間的任意時刻屿良。
示例:
我們接著上面的例子,將圖層的圓角值做一些改變惫周。
@interface ViewController ()
@property (strong, nonatomic)CALayer* colorLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 修改圓角屬性
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
animation.toValue = @(self.colorLayer.bounds.size.height/2.0);
animation.duration = 2;
animation.autoreverses = YES; // 執(zhí)行逆動畫
[self.colorLayer addAnimation:animation forKey:@"cornerRadius_animation"];
}
注:animationWithKeyPath:
所帶的字符串表示需要修改的 layer 可動畫的屬性尘惧,不是隨便寫的字符串,一般常用的可動畫屬性如下:
key | 說明 | 使用樣例 |
---|---|---|
transform.scale | 縮放 | @(0.5) |
transform.scale.x | 寬的比例 | @(0.5) |
transform.scale.y | 高的比例 | @(0.5) |
opacity | 透明度 | @(0.5) |
cornerRadius | 圓角的設(shè)置 | @(50) |
transform.rotation.x | 圍繞x軸旋轉(zhuǎn) | @(M_PI) |
transform.rotation.y | 圍繞y軸旋轉(zhuǎn) | @(M_PI) |
transform.rotation.z | 圍繞z軸旋轉(zhuǎn) | @(M_PI) |
strokeStart | 結(jié)合CAShapeLayer使用 | 賦值多變 |
strokeEnd | 結(jié)合CAShapeLayer使用 | 賦值都變 |
bounds | 大小递递,中心不變 | [NSValue valueWithCGRect:CGRectMake(0, 0, 100, 100)]; |
position | 位置(中心點的改變) | [NSValue valueWithCGPoint:CGPointMake(100, 100)]; |
contents | 內(nèi)容喷橙, | 比如UIImageView的圖片 imageAnima.toValue = (id)[UIImage imageNamed:@”imageName”].CGImage; |
…… |
動畫開始和完成事件
和隱式動畫中的完成回調(diào)不同,CAAnimation 采用了委托模式漾狼,因此你如果需要處理動畫的開始和完成事件時重慢,你需要完成 CAAnimationDelegate 的代理方法:
- (void)animationDidStart:(CAAnimation *)anim;
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
其中 flag 標(biāo)識了動畫是否是正常結(jié)束。另外逊躁,事件傳遞不是完成block塊似踱,而是采用委托模式會帶來一個問題,就是你有多個動畫時稽煤,你需要判斷當(dāng)前是那個圖層的動畫事件核芽。
這里提供兩個用來區(qū)別的方案。一種就是在添加動畫時酵熙,-addAnimation:forKey:
設(shè)置每個動畫對應(yīng)不同的key值轧简,然后通過 animationKeys
獲取到圖層上所有的動畫key,然后對每個圖層循環(huán)所有建匾二,通過 -animationForKey:
找到結(jié)果哮独。顯然這種是非常的麻煩的方式拳芙。好在 CAAnimation 實現(xiàn)了 KVC 協(xié)議,我們可以像使用字典一樣皮璧,隨意的存取屬性舟扎。
示例:
我們將圖層在完成動畫之后,進行背景色的更改悴务。
@interface ViewController ()<CAAnimationDelegate>
@property (strong, nonatomic)CALayer* colorLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:self.colorLayer];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
animation.toValue = @(self.colorLayer.bounds.size.height/2.0);
animation.duration = 2;
animation.autoreverses = YES; // 執(zhí)行逆動畫
animation.delegate = self;
// 將視圖附加到動畫上
[animation setValue:self.colorLayer forKey:@"colorLayer"];
[self.colorLayer addAnimation:animation forKey:@"cornerRadius_animation"];
}
// 動畫結(jié)束事件
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
// 通過key值睹限,取回附加的視圖
CALayer* layer = [anim valueForKey:@"colorLayer"];
layer.backgroundColor = UIColor.redColor.CGColor;
}
- CAKeyframeAnimation
相比于經(jīng)典動畫關(guān)注于起始和終止的狀態(tài)值,關(guān)鍵幀動畫更注重整個動畫過程中多個關(guān)鍵點的狀態(tài)讯檐,因此關(guān)鍵幀動畫需要一連串的值來做動畫羡疗,你甚至可以說,經(jīng)典動畫是關(guān)鍵動畫的一種别洪。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
animation.duration = 4;
animation.values = @[
(__bridge id)UIColor.blueColor.CGColor,
(__bridge id)UIColor.redColor.CGColor,
(__bridge id)UIColor.yellowColor.CGColor,
(__bridge id)UIColor.greenColor.CGColor,
(__bridge id)UIColor.blueColor.CGColor
];
[self.colorLayer addAnimation:animation forKey:nil];
}
上述例子演示了給予關(guān)鍵幀動畫的關(guān)鍵位置的數(shù)組值叨恨,實際上,關(guān)鍵幀還可以是無數(shù)個位置蕉拢,如果此時的動畫屬性是針對位置一類的特碳,我們就可以將這些關(guān)鍵幀看作是路徑,這就演變出了另一種方式做動畫晕换,即 path
午乓。
下面通過移動圖層來演示這種方式的關(guān)鍵幀動畫:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.duration = 4.0;
// 關(guān)鍵幀路徑
animation.path = self.path.CGPath;
[self.imageLayer addAnimation:animation forKey:nil];
}
我們的飛船可以沿著關(guān)鍵路徑進行移動,但是我們發(fā)現(xiàn)飛船的方向一直是橫向的闸准,就如最初設(shè)置的方向益愈,而不是指向曲線切線的方向。好在蘋果發(fā)現(xiàn)了這一點夷家,并且給 CAKeyFrameAnimation 添加了一個 rotationMode 的屬性蒸其,設(shè)置它為常量 kCAAnimationRotateAuto,圖層將會根據(jù)曲線的切線自動旋轉(zhuǎn)库快。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.duration = 4.0;
animation.path = self.path.CGPath;
animation.rotationMode = kCAAnimationRotateAuto;
[self.imageLayer addAnimation:animation forKey:nil];
}
- CASpringAnimation
CABasicAnimation 動畫的子類摸袁,可以實現(xiàn)彈性動畫。這個動畫是在 iOS9 之后才出現(xiàn)的义屏,UIView 有其對應(yīng)的動畫塊靠汁。
CASpringAnimation 通過幾個物理相關(guān)屬性來計算出圖層執(zhí)行的動畫效果。這些屬性如下:
mass
:質(zhì)量闽铐,影響慣性蝶怔、拉伸幅度
stiffness
:剛度系數(shù),剛度系數(shù)越大兄墅,形變產(chǎn)生的力就越大踢星,運動越快
damping
:阻尼系數(shù),阻止彈簧伸縮的系數(shù)隙咸,阻尼系數(shù)越大沐悦,停止越快
initialVelocity
:初始速率
settlingDuration
:(只讀)結(jié)算時間成洗,根據(jù)當(dāng)前的動畫參數(shù)估算彈簧動畫到停止時的時間
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"position.y"];
animation.damping = 5; // 阻尼系數(shù)
animation.stiffness = 100; // 剛度系數(shù)
animation.mass = 1; // 質(zhì)量
animation.initialVelocity = 0; // 初始速率
animation.duration = animation.settlingDuration; //結(jié)束時間
animation.fromValue = @(self.subLayer.position.y);
animation.toValue = @(self.subLayer.position.y+100);
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.subLayer addAnimation:animation forKey:nil];
}
注:動畫并沒會改變圖層和視圖的 frame,因此在執(zhí)行完動畫后藏否,都默認(rèn)被重置到最初的位置泌枪。如果你需要動畫執(zhí)行完之后保持當(dāng)前的位置狀態(tài),可以設(shè)置 removedOnCompletion
為 NO秕岛,并設(shè)置 fillMode
模式為 kCAFillModeForwards
。
動畫組
之前提到的幾個屬性動畫误证,都僅僅是作用于單一屬性继薛,但是如果我們需要幾個動畫一起作用到圖層上,該怎么辦呢愈捅?蘋果為我們提供了一個組合動畫 CAAnimationGroup
遏考,他是另一個繼承 CAAnimation
的子類,和幾個屬性動畫的父類同級蓝谨,它只有一個屬性 animations
數(shù)組灌具,就是用來存放多個動畫的。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 動畫1
CABasicAnimation* animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation1.toValue = @(0.5);
// 動畫2
CABasicAnimation* animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation2.toValue = @(M_PI*2);
// 動畫組
CAAnimationGroup* group = [CAAnimationGroup animation];
// 設(shè)置所有的動畫的執(zhí)行時間
group.duration = 1.5;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
// 將所有動畫都添加到組中
group.animations = @[animation1,animation2];
[self.subLayer addAnimation:group forKey:nil];
}
需要注意的是譬巫,動畫組的動畫時間取所有動畫最短時間咖楣,超出時間的部分會立刻被停止,因此使用動畫組的時候芦昔,最好是一些動畫時間統(tǒng)一的組合诱贿,比如上面例子中,動畫時間并非由某個動畫來決定咕缎,而是由動畫組來設(shè)置珠十。
過渡動畫
屬性動畫只會對圖層的一些可動畫的屬性起到作用,當(dāng)我們想要改變一個不能動畫的屬性(比如圖片)凭豪,或者從層級關(guān)系中添加或者移除圖層(過場效果)焙蹭,屬性動畫將不起作用。因此嫂伞,蘋果又提供了一個用來做過渡動畫的類 CATransition
孔厉,注意這個類和上面提到的事物 CATransaction
不是同一個東西。
CATransition
是 CAAnimation 的子類末早,它由兩個過渡類型來控制變換效果烟馅,一個是 type
:用來控制過渡效果,一個是 subtype
:用來控制過渡的方向然磷。
type
的幾種類型:
kCATransitionFade // 默認(rèn)郑趁,漸變消失
kCATransitionMoveIn // 從當(dāng)前圖層上面劃入
kCATransitionPush // 當(dāng)前圖層被推出,用新值替換
kCATransitionReveal // 從當(dāng)前圖層上面劃出姿搜,效果和 kCATransitionMoveIn 相反
除了系統(tǒng)開放出來的四種類型寡润,還有幾種私有API捆憎,可以通過字符串來設(shè)置:
cube //立方體翻滾效果
oglFlip //上下左右翻轉(zhuǎn)效果
suckEffect //收縮效果,如一塊布被抽走(不支持過渡方向)
rippleEffect //滴水效果(不支持過渡方向)
pageCurl //向上翻頁效果
pageUnCurl //向下翻頁效果
cameraIrisHollowOpen //相機鏡頭打開效果(不支持過渡方向)
cameraIrisHollowClose //相機鏡頭關(guān)上效果(不支持過渡方向)
subtype
的幾種類型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
示例:
我們來使用過渡動畫來切換幾張圖片
// 獲取隨機整數(shù)
#define randomFromAtoB(A,B) (int)(A+(arc4random()%(B-A+1)))
@interface ViewController ()
{
NSInteger currentIndex;
}
@property (strong, nonatomic) CALayer* subLayer;
@property (strong,nonatomic) NSArray *images; // 圖片數(shù)組
@property (strong,nonatomic) NSArray *animations; // 動畫類型數(shù)組
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view.layer addSublayer:self.subLayer];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CATransition *transition = [CATransition new];
// 設(shè)置動畫類型,注意對于蘋果官方?jīng)]公開的動畫類型只能使用字符串梭纹,并沒有對應(yīng)的常量定義
transition.type = self.animations[randomFromAtoB(0, self.animations.count-1)];
// 設(shè)置子類型,方向
transition.subtype = @[kCATransitionFromRight,
kCATransitionFromLeft,
kCATransitionFromTop,
kCATransitionFromBottom][randomFromAtoB(0, 3)];
// 設(shè)置動畫時間
transition.duration = 1.0;
// 添加新的視圖
currentIndex = (currentIndex+1)%self.images.count;
NSString *imageName = self.images[currentIndex];
self.subLayer.contents = (__bridge id)[UIImage imageNamed:imageName].CGImage;
[self.subLayer addAnimation:transition forKey:@"KCATransitionAnimation"];
}
-(CALayer *)subLayer{
if (_subLayer==nil) {
_subLayer = [CALayer new];
_subLayer.frame = self.view.bounds;
_subLayer.backgroundColor = UIColor.blueColor.CGColor;
_subLayer.contents = (__bridge id)[UIImage imageNamed:@"0.jpg"].CGImage;
}
return _subLayer;
}
-(NSArray *)animations{
if (_animations == nil) {
_animations = @[@"fade", // 淡出效果
@"movein", // 新視圖移動到舊視圖
@"push", // 新視圖推出到舊視圖
@"reveal", // 移開舊視圖現(xiàn)實新視圖
@"cube", // 立方體翻轉(zhuǎn)效果
@"oglFlip", // 翻轉(zhuǎn)效果
@"suckEffect", // 吸收效果
@"rippleEffect", // 水滴效果
@"pageCurl", // 向上翻頁
@"pageUnCurl", // 向下翻頁
@"cameralIrisHollowOpen", // 攝像頭打開
@"cameraIrisHollowClose", // 攝像頭關(guān)閉
];
}
return _animations;
}
-(NSArray *)images{
if (_images==nil) {
_images = @[@"0.jpg",
@"1.jpg",
@"2.jpg",
@"3.jpg",
@"4.jpg",
@"5.jpg"];
}
return _images;
}
CATransition 并不作用于指定的圖層屬性躲惰,這就是說你可以在即使不能準(zhǔn)確得知改變了什么的情況下對圖層做動畫,例如变抽,在不知道 UITableView 哪一行被添加或者刪除的情況下础拨,直接就可以平滑地刷新它,又如在 UITabBarController 切換視圖時添加上過渡動畫绍载,可以比如淡入淡出的效果诡宗,又或者在不知道 UIViewController 內(nèi)部的視圖層級的情況下對兩個不同的實例做過渡動畫。
這些例子和我們之前所討論的情況完全不同击儡,因為它們不僅涉及到圖層的屬性塔沃,而且是整個圖層樹的改變--我們在這種動畫的過程中手動在層級關(guān)系中添加或者移除圖層。
自定義過渡動畫
過渡動畫做基礎(chǔ)的原則就是對原始的圖層外觀截圖阳谍,然后添加一段動畫蛀柴,平滑過渡到圖層改變之后那個截圖的效果。如果我們對圖層截圖矫夯,就可以使用屬性動畫來代替 CATransition 或者是 UIKit 的過渡方法來實現(xiàn)動畫鸽疾。
CALayer 有一個 -renderInContext:
方法,可以通過把它繪制到 Core Graphics 的上下文中捕獲當(dāng)前內(nèi)容的圖片茧痒,然后在另外的視圖中顯示出來肮韧。如果我們把這個截屏視圖置于原始視圖之上,就可以遮住真實視圖的所有變化旺订,于是重新創(chuàng)建了一個簡單的過渡效果弄企。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 獲取當(dāng)前屏幕的截圖
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
//insert snapshot view in front of this one
UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
coverView.frame = self.view.bounds;
// 將截圖覆蓋到當(dāng)前視圖上
[self.view addSubview:coverView];
// 為了演示過渡效果,我們修改一下當(dāng)前視圖的背景色区拳,以區(qū)分之前的視圖
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
// 執(zhí)行過渡動畫
[UIView animateWithDuration:0.75 animations:^{
CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 1);
coverView.transform = transform;
} completion:^(BOOL finished) {
// 最后移除掉障眼法的圖層
[coverView removeFromSuperview];
}];
}
取消動畫
在我們使用-addAnimation:forKey:
方法中的key參數(shù)來在添加動畫之后檢索一個動畫拘领,使用如下方法:
- (CAAnimation *)animationForKey:(NSString *)key;
但并不支持在動畫運行過程中修改動畫,所以這個方法主要用來檢測動畫的屬性樱调,或者判斷它是否被添加到當(dāng)前圖層中约素。
為了終止一個指定的動畫,你可以用如下方法把它從圖層移除掉:
- (void)removeAnimationForKey:(NSString *)key;
也可以根據(jù)需要移除所有動畫:
- (void)removeAllAnimations;
動畫一旦被移除笆凌,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值圣猎。一般說來,動畫在結(jié)束之后被自動移除乞而,除非設(shè)置 removedOnCompletion 為NO送悔,如果你設(shè)置動畫在結(jié)束之后不被自動移除,那么當(dāng)它不需要的時候你要手動移除它;否則它會一直存在于內(nèi)存中欠啤,直到圖層被銷毀荚藻。
時間相關(guān)
CAMediaTiming 協(xié)議
CAMediaTiming 協(xié)議定義了在一段動畫內(nèi)用來控制逝去時間的屬性的集合, CALayer 和 CAAnimation 都實現(xiàn)了這個協(xié)議洁段,所以時間可以被任意基于一個圖層或者一段動畫的類控制应狱。
CAAnimation 中幾個常用的屬性:
duration
:
duration(CAMediaTiming的屬性之一),duration是一個 CFTimeInterval 的類型(類似于 NSTimeInterval 的一種雙精度浮點類型)祠丝,對將要進行的動畫的一次迭代指定了時間疾呻。
repeatCount
:
代表動畫重復(fù)的迭代次數(shù)。
repeatDuration
:
它讓動畫重復(fù)一個指定的時間写半,而不是指定次數(shù)罐韩。
autoreverses
:
在每次間隔交替循環(huán)過程中自動回放。在設(shè)置此值為 YES 時污朽,duration 的一半時間會用來做自動回放。
相對時間的幾個屬性:
在 Core Animation 中龙考,時間都是相對的蟆肆,每個動畫都有它自己描述的時間,可以獨立地加速晦款,延時或者偏移炎功。
beginTime
:
指定了動畫開始之前的的延遲時間。這里的延遲從動畫添加到可見圖層的那一刻開始測量缓溅,默認(rèn)是0(就是說動畫會立刻執(zhí)行)蛇损。
speed
:
是一個時間的倍數(shù),默認(rèn)1.0坛怪,減少它會減慢圖層/動畫的時間淤齐,增加它會加快速度。如果2.0的速度袜匿,那么對于一個 duration 為1的動畫更啄,實際上在0.5秒的時候就已經(jīng)完成了。
特別的居灯,前面提到到 CALayer 也實現(xiàn)了 CAMediaTiming 協(xié)議祭务,如果把圖層的 speed 的值設(shè)置為0,它會暫停任何添加到圖層上的動畫怪嫌,如果 speed 的值大于1.0則變現(xiàn)為快進义锥,如果設(shè)置成一個負(fù)值則變?yōu)榈够氐膭赢嫛?/p>
如果設(shè)置主 window 圖層的 speed 為0時,可以將整個應(yīng)用程序的動畫暫停岩灭。同樣的拌倍,如果我們將其設(shè)置為快進,就可以完成加速多有的視圖動畫來進行自動化測試川背。設(shè)置代碼如下:
self.window.layer.speed = 100;
timeOffset
:
和 beginTime 類似贰拿,但是和增加 beginTime 導(dǎo)致的延遲動畫不同蛤袒,增加 timeOffset 只是讓動畫快進到某一點,例如膨更,對于一個持續(xù)1秒的動畫來說妙真,設(shè)置 timeOffset 為0.5意味著動畫將從一半的地方開始。
timeOffset 一個很有用的功能在于你可以它可以讓你手動控制動畫進程荚守,通過設(shè)置 speed 為0珍德,可以禁用動畫的自動播放,然后來使用 timeOffset 來來回顯示動畫序列矗漾。這可以使得運用手勢來手動控制復(fù)雜動畫或者多個圖層的動畫組變得很簡單锈候。
動畫結(jié)束后的填充模式:
fillMode
:
kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved
這個屬性表示動畫結(jié)束之后,是保持動畫最開始的那一幀還是保持動畫結(jié)束之后的那一幀敞贡。默認(rèn)情況是kCAFillModeRemoved
泵琳。
緩沖過渡
CAMediaTimingFunction
動畫時間決定了圖層變換的時長,而動畫的速度表示動畫執(zhí)行的“速率”誊役,通常是變化量和時間的比值获列。這里的變化量可以是圖層移動的距離,縮放的大小蛔垢,也可以是圖層的透明度击孩、填充色等。實際上鹏漆,任意的可以做動畫的屬性的變化差值都可以稱作變化量巩梢。
默認(rèn)情況下,我們的動畫都是線性變化的艺玲,即速率是恒定不變的括蝠,就如前面的那些示例,但是有時候饭聚,我們并不希望動畫的速度一層不變又跛,那么該怎么做呢?幸運的事若治,Core Animation 已經(jīng)為我們設(shè)計了一系列標(biāo)準(zhǔn)函數(shù)提供給我們使用慨蓝。
timingFunction
:
CAAnimation 的 timingFunction 屬性,是 CAMediaTimingFunction 類的一個對象端幼。(如果想改變隱式動畫的計時函數(shù)礼烈,同樣也可以使用CATransaction的+setAnimationTimingFunction:
方法)
我們可以通過下面的構(gòu)造函數(shù)來創(chuàng)建緩沖對象:
+ (instancetype)functionWithName:(CAMediaTimingFunctionName)name;
傳入如下幾個常量之一:
kCAMediaTimingFunctionLinear // 線性
kCAMediaTimingFunctionEaseIn // 緩慢起步
kCAMediaTimingFunctionEaseOut // 緩慢停止
kCAMediaTimingFunctionEaseInEaseOut // 先慢起步后快最后慢停止
kCAMediaTimingFunctionDefault // 類似于上面
示例:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
animation.duration = 2.0;
animation.toValue = @(self.subLayer.frame.origin.y+400);
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.subLayer addAnimation:animation forKey:nil];
}
上圖的緩沖模式分別為:
kCAMediaTimingFunctionLinear
kCAMediaTimingFunctionEaseIn
kCAMediaTimingFunctionEaseOut
kCAMediaTimingFunctionEaseInEaseOut
CAKeyframeAnimation 有一個NSArray類型的 timingFunctions 屬性,我們可以用它來對每次動畫的步驟指定不同的計時函數(shù)婆跑。但是指定函數(shù)的個數(shù)一定要等于 keyframes 數(shù)組的元素個數(shù)減一此熬,因為它是描述每一幀之間動畫速度的函數(shù)。
自定義緩沖函數(shù)
在上一節(jié)中,介紹了幾個系統(tǒng)為我們定義好的緩沖函數(shù)犀忱,能適用于大部分的應(yīng)用環(huán)境募谎。我們注意到,除了 +functionWithName:
之外阴汇,CAMediaTimingFunction 同樣有另一個構(gòu)造函數(shù)数冬,一個有四個浮點參數(shù)的 +functionWithControlPoints::::
,使用這個方法搀庶,我們可以創(chuàng)建一個自定義的緩沖函數(shù)拐纱,來匹配我們的動畫。
CAMediaTimingFunction 函數(shù)的主要原則在于它把輸入的時間轉(zhuǎn)換成起點和終點之間成比例的改變哥倔。我們可以用一個簡單的圖標(biāo)來解釋秸架,橫軸代表時間,縱軸代表改變的量咆蒿,于是線性的緩沖就是一條從起點開始的簡單的斜線东抹。
這條曲線的斜率代表了速度,斜率的改變代表了加速度沃测,原則上來說府阀,任何加速的曲線都可以用這種圖像來表示,但是 CAMediaTimingFunction 使用了一個叫做三次貝塞爾曲線的函數(shù)芽突,它只可以產(chǎn)出指定緩沖函數(shù)的子集。
三次貝塞爾緩沖函數(shù)表達出先加速董瞻,然后減速寞蚌,最后快到達終點的時候又加速的情況,那么標(biāo)準(zhǔn)的緩沖函數(shù)又該如何用圖像來表示呢钠糊?
CAMediaTimingFunction 有一個叫做-getControlPointAtIndex:values:
的方法挟秤,可以用來檢索曲線的點,使用它我們可以找到標(biāo)準(zhǔn)緩沖函數(shù)的點抄伍,然后用 UIBezierPath 和 CAShapeLayer 來把它畫出來艘刚。
曲線的起始和終點始終是{0, 0}和{1, 1},于是我們只需要檢索曲線的第二個和第三個點(控制點)截珍。
- (void)viewDidLoad{
[super viewDidLoad];
CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
// 獲取到兩個控制點
CGPoint controlPoint1, controlPoint2;
[function getControlPointAtIndex:1 values:(float *)&controlPoint1];
[function getControlPointAtIndex:2 values:(float *)&controlPoint2];
// 創(chuàng)建曲線
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointZero];
[path addCurveToPoint:CGPointMake(1, 1)
controlPoint1:controlPoint1 controlPoint2:controlPoint2];
// 轉(zhuǎn)換點攀甚,讓其可見
[path applyTransform:CGAffineTransformMakeScale(200, 200)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 4.0f;
shapeLayer.path = path.CGPath;
[self.layerView.layer addSublayer:shapeLayer];
self.layerView.layer.geometryFlipped = YES;
}
所有的標(biāo)準(zhǔn)緩沖函數(shù)的圖像如下: