iOS动画-CAAnimation使用详解
理解了隱式動(dòng)畫后,顯式動(dòng)畫就更加通俗易懂了。區(qū)別于隱式動(dòng)畫的特點(diǎn),顯式動(dòng)畫就是需要我們明確指定類型、時(shí)間等參數(shù)來(lái)實(shí)現(xiàn)效果的動(dòng)畫。除此之外,我們也可以創(chuàng)建非線性動(dòng)畫,比如沿著任意一條曲線運(yùn)動(dòng)等;
我們平時(shí)最常用的也是顯式動(dòng)畫,不僅系統(tǒng)為我們的視圖提供了UIViewAnimationWithBlock的動(dòng)畫封裝,而且我們?cè)谑煜ち薈ore Animation的動(dòng)畫屬性后也可以很方便的設(shè)置顯式動(dòng)畫;
本篇主要內(nèi)容:
1.iOS動(dòng)畫的分類
2.CAMediaTiming協(xié)議
3.CAAnimation基類
4.CAPropertyAnimation基類
5.基礎(chǔ)動(dòng)畫CABasicAnimation
6.關(guān)鍵幀動(dòng)畫CAKeyframeAnimation
7.動(dòng)畫組CAGroupAnimation
8.過(guò)渡動(dòng)畫CATransition
9.委托模式下的動(dòng)畫區(qū)分
10.虛擬屬性及其作用
11.動(dòng)畫的取消
相關(guān)文章:
iOS動(dòng)畫-CALayer寄宿圖與繪制原理
iOS動(dòng)畫-CALayer布局屬性詳解
iOS動(dòng)畫-CALayer隱式動(dòng)畫原理與特性
iOS動(dòng)畫-CAAnimation使用詳解
一、動(dòng)畫的分類
1、實(shí)現(xiàn)動(dòng)畫的方式
如果根據(jù)實(shí)現(xiàn)動(dòng)畫時(shí)直接操作對(duì)象的類型,我們可以簡(jiǎn)單的將動(dòng)畫分為視圖和圖層兩種;但事實(shí)上,無(wú)論UIViewAnimaiton動(dòng)畫還是UIViewAnimaitonWithBlock動(dòng)畫都只是對(duì)UIView的關(guān)聯(lián)圖層CALayer動(dòng)畫的進(jìn)一步封裝。
實(shí)現(xiàn)動(dòng)畫的方式.png
2.核心動(dòng)畫Core Animation常用類的繼承關(guān)系
我們?cè)谑褂肅ore Animation動(dòng)畫之前,有必要對(duì)核心動(dòng)畫常見(jiàn)的類和動(dòng)畫屬性做一個(gè)基本了解;從繼承關(guān)系的圖示中,我們可以十分清晰的看出這些屬性設(shè)置設(shè)置因何而來(lái),以及它們各自的聯(lián)系。
核心動(dòng)畫類的繼承關(guān)系.jpg
| CAMediaTiming | 協(xié)議;定義了一段動(dòng)畫內(nèi)用于控制時(shí)間的屬性的集合 |
| CAAnimation | 抽象類;作為所有動(dòng)畫類型父類,不可直接使用 |
| CAPropertyAnimation | 抽象類;作為基礎(chǔ)動(dòng)畫和幀動(dòng)畫的父類,不可直接使用 |
| CABasicAnimation | 基礎(chǔ)動(dòng)畫;用于實(shí)現(xiàn)單一屬性變化的動(dòng)畫 |
| CAKeyFrameAnimation | 關(guān)鍵幀動(dòng)畫;用于實(shí)現(xiàn)單一屬性連續(xù)變化的動(dòng)畫 |
| CAAnimaitionGroup | 組動(dòng)畫;用于實(shí)現(xiàn)多屬性同時(shí)變化的動(dòng)畫 |
| CATrasition | 轉(zhuǎn)場(chǎng)過(guò)渡動(dòng)畫; |
二、CAMediaTiming協(xié)議
CAMediaTiming協(xié)議定義了一段動(dòng)畫內(nèi)用于控制時(shí)間的屬性的集合,CALayer和CAAnimation都實(shí)現(xiàn)了這個(gè)協(xié)議,所以時(shí)間可以被任意基于一個(gè)圖層或者一段動(dòng)畫的類控制,有關(guān)CAMediaTimg協(xié)議具體的屬性如下:
| beginTime | CFTimeInterval | 動(dòng)畫開(kāi)始之前的延遲時(shí)間,這里的延遲從動(dòng)畫添加到可見(jiàn)圖層上那一刻開(kāi)始測(cè)量; (設(shè)置動(dòng)畫beginTime為1,動(dòng)畫將延時(shí)1秒后開(kāi)始執(zhí)行) |
| duration | CFTimeInterval | 動(dòng)畫持續(xù)時(shí)間; (默認(rèn)值為0,但是實(shí)際動(dòng)畫默認(rèn)持續(xù)時(shí)間為0.25秒) |
| speed | float | 動(dòng)畫執(zhí)行的速度; (默認(rèn)值為0,減少它會(huì)減慢動(dòng)畫的時(shí)間,增加它會(huì)加快速度) (設(shè)置speed為2時(shí),則動(dòng)畫實(shí)際執(zhí)行時(shí)間是duration的一半) |
| timeOffset | CFTimeInterval | 動(dòng)畫時(shí)間偏移量; (設(shè)置時(shí)長(zhǎng)3秒動(dòng)畫的timeOffset為1時(shí),動(dòng)畫會(huì)從1秒位置執(zhí)到最后,再執(zhí)行之前跳過(guò)的部分) |
| repeatCount | float | 動(dòng)畫重復(fù)次數(shù);默認(rèn)值是0,但是實(shí)際默認(rèn)動(dòng)畫執(zhí)行1次; (設(shè)置為INFINITY,則一直執(zhí)行); (duration是2,repeatCount設(shè)置為3.5,則完整動(dòng)畫時(shí)長(zhǎng)7秒) |
| repeatDuration | CFTimeInterval | 動(dòng)畫重復(fù)的時(shí)間,讓動(dòng)畫重復(fù)執(zhí)行一個(gè)指定的時(shí)間; (設(shè)置為INFINITY,一直執(zhí)行) repeatCount和repeatDuration可能會(huì)相互沖突,所以你只需要對(duì)其中一個(gè)指定非零值,對(duì)兩個(gè)屬性都設(shè)置非0值的行為沒(méi)有被定義; |
| autoreverses | BOOL | 動(dòng)畫從初始值執(zhí)行到最終值,是否會(huì)反向回到初始值; (設(shè)置為YES,動(dòng)畫完成后將以動(dòng)畫的形式回到初始位置) |
| fillMode | NSStrinng | 決定當(dāng)前對(duì)象在非動(dòng)畫時(shí)間端段的動(dòng)畫屬性值,如動(dòng)畫開(kāi)始之前和動(dòng)畫結(jié)束之后 |
1.fillMode詳細(xì)說(shuō)明
試想這樣一個(gè)問(wèn)題:在beginTime非0(即動(dòng)畫未真正執(zhí)行之前),以及removeOnCompletion被設(shè)置為NO的動(dòng)畫結(jié)束時(shí),我們會(huì)遇到這樣一個(gè)問(wèn)題:被設(shè)置動(dòng)畫的屬性應(yīng)該是什么值?
一種可能是屬性與動(dòng)畫沒(méi)被添加之前保持一致,還有一種可能是保持動(dòng)畫開(kāi)始之前那一幀或者動(dòng)畫結(jié)束那一幀,這就是所謂的填充。
CAMediaTiming的fillMode用來(lái)控制填充效果,它是一個(gè)NSString類型,有四種常量可供使用:
| kCAFillModeRemoved (default) | NSString | 默認(rèn)值,動(dòng)畫開(kāi)始前和結(jié)束后,動(dòng)畫對(duì)圖層都沒(méi)有影響,圖層依然保持初始值 |
| kCAFillModeForwards | NSString | 動(dòng)畫結(jié)束后,圖層一直保持動(dòng)畫后的最終狀態(tài) |
| kCAFillModeBackwards | NSString | 動(dòng)畫開(kāi)始前,只要加入動(dòng)畫就會(huì)處于動(dòng)畫的初始狀態(tài) |
| kCAFillModeBoth | NSString | 綜合了kCAFillModeForwards與kCAFillModeBackwards特性; (動(dòng)畫加入圖層到真正執(zhí)行動(dòng)畫的時(shí)間段里,圖層保持動(dòng)畫初始狀態(tài);動(dòng)畫結(jié)束之后保持動(dòng)畫最終狀態(tài)) |
特別注意:removedOnCompletion需要設(shè)置為NO,否則fillMode不起作用;
2.CAMediaTiming屬性應(yīng)用總結(jié)
時(shí)間屬性的綜合應(yīng)用.png
三、CAAnimation基類
CAAnimation作為所有動(dòng)畫類型父類,是一個(gè)抽象類;我們不能直接使用CAAnimation類,而是使用它的子類;關(guān)于它的定義如下:
@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動(dòng)畫基類遵循了CAMediaTiming協(xié)議,而且另外包含了三個(gè)常用的動(dòng)畫屬性;下面是對(duì)這三個(gè)屬性的總結(jié):
1.動(dòng)畫緩沖屬性timingFunction
動(dòng)畫實(shí)際上就是在一段時(shí)間內(nèi)隨著某個(gè)特定速率執(zhí)行變化的過(guò)程,現(xiàn)實(shí)中的任何物體都會(huì)在運(yùn)動(dòng)中經(jīng)歷加速或者減速的過(guò)程,而不是速度驟變;因此,CoreAnimation也內(nèi)嵌了一系列標(biāo)準(zhǔn)的緩沖函數(shù)來(lái)使動(dòng)畫看起來(lái)更平滑自然,這就是我們要說(shuō)到的動(dòng)畫緩沖。
timingFunction屬性是CAMediaTimingFunction類的一個(gè)對(duì)象,用來(lái)控制圖層動(dòng)畫變換的速度;使用它需要調(diào)用+functionWithName:的構(gòu)造方法,下面是可傳入的變量的介紹:
| KCAMediaTimingFuncationLinear | 默認(rèn),勻速執(zhí)行動(dòng)畫 |
| KCAMediaTimingFuncationEaseIn | 先慢慢加速,后突然停止 |
| KCAMediaTimingFuncationEaseOut | 先全速開(kāi)始,再慢慢減速停止 |
| KCAMediaTimingFuncationEaseInEaseOut | 先慢慢加速,再慢慢減速 |
| KCAMediaTimingFuncationDefault | 效果同KCAMediaTimingFuncationEaseInEaseOut |
這五種不同的緩沖效果如下:
動(dòng)畫緩沖屬性timingFunction.jpg
通過(guò)這種方法控制動(dòng)畫速度,其實(shí)是使用不同的變量創(chuàng)建了不同的計(jì)時(shí)函數(shù)。比如KCAMediaTimingFuncationLinear選項(xiàng)創(chuàng)建的是一個(gè)線性的計(jì)時(shí)函數(shù),這也是CAAnimation的timingFunction屬性為空時(shí)候的默認(rèn)函數(shù)。
注意:KCAMediaTimingFuncationDefault相比KCAMediaTimingFuncationEaseInEaseOut的加速和減速過(guò)程稍微有些慢,兩者區(qū)別很難察覺(jué);可能蘋果也覺(jué)得它更適合用于隱式動(dòng)畫,就作為了隱式動(dòng)畫的默認(rèn)效果;但是創(chuàng)建顯式的CAAnimation時(shí),KCAMediaTimingFuncationLinear才是默認(rèn)效果而非KCAMediaTimingFuncationDefault;
UIKit動(dòng)畫其實(shí)也同樣支持這些緩沖效果的使用,在我們使用UIViewAnimationBlock實(shí)現(xiàn)動(dòng)畫的時(shí)候,可以給options參數(shù)提供了如下的常量來(lái)修改緩沖效果:
| UIViewAnimationOptionCurveLinear | 默認(rèn),勻速執(zhí)行動(dòng)畫 |
| UIViewAnimationOptionCurveEaseIn | 先慢慢加速,后突然停止 |
| UIViewAnimationOptionCurveEaseOut | 先全速開(kāi)始,再慢慢減速停止 |
| UIViewAnimationOptionCurveEaseInOut | 先慢慢加速,再慢慢減速 |
2.動(dòng)畫代理屬性delegate
/* Delegate methods for CAAnimation. */ @protocol CAAnimationDelegate <NSObject>@optional //動(dòng)畫開(kāi)始時(shí)調(diào)用 - (void)animationDidStart:(CAAnimation *)anim; //動(dòng)畫結(jié)束時(shí)調(diào)用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; @end3.removedOnCompletion
removedOnCompletion屬性默認(rèn)為YES,表示動(dòng)畫完成后就會(huì)從圖層上移除,圖層也會(huì)恢復(fù)到動(dòng)畫執(zhí)行前的狀態(tài);當(dāng)其修改為NO時(shí),那么圖層將會(huì)保持動(dòng)畫結(jié)束后的狀態(tài),此時(shí)的fillMode屬性也將生效;
另外,removedOnCompletion設(shè)置為NO時(shí),直到我們手動(dòng)移除動(dòng)畫,否則動(dòng)畫將不會(huì)自動(dòng)釋放;所以通常我們此時(shí)會(huì)給動(dòng)畫添加一個(gè)非空的鍵,這樣可以在不需要?jiǎng)赢嫷臅r(shí)候把它從圖層上移除;
當(dāng)我們需要防止動(dòng)畫結(jié)束后回到初始狀態(tài)時(shí),
只需設(shè)置removedOnCompletion、fillMode兩個(gè)屬性就可以了。
transformAnima.removedOnCompletion = NO; transformAnima.fillMode = kCAFillModeForwards;解釋:為什么動(dòng)畫結(jié)束后返回原狀態(tài)?
首先我們需要搞明白一點(diǎn)的是,layer動(dòng)畫運(yùn)行的過(guò)程是怎樣的?其實(shí)在我們給一個(gè)視圖添加layer動(dòng)畫時(shí),真正移動(dòng)并不是我們的視圖本身,而是 presentation layer 的一個(gè)緩存。動(dòng)畫開(kāi)始時(shí) presentation layer開(kāi)始移動(dòng),原始layer隱藏,動(dòng)畫結(jié)束時(shí),presentation layer從屏幕上移除,原始layer顯示。這就解釋了為什么我們的視圖在動(dòng)畫結(jié)束后又回到了原來(lái)的狀態(tài),因?yàn)樗揪蜎](méi)動(dòng)過(guò)。
這個(gè)同樣也可以解釋為什么在動(dòng)畫移動(dòng)過(guò)程中,我們?yōu)楹尾荒軐?duì)其進(jìn)行任何操作。我們點(diǎn)擊動(dòng)畫不能響應(yīng)事件,因?yàn)橐苿?dòng)的只是layer, view還是在原始的位置.
所以在我們完成layer動(dòng)畫之后,最好將我們的layer屬性設(shè)置為我們最終狀態(tài)的屬性,然后將presentation layer 移除掉。
四、CAPropertyAnimation基類
CAPropertyAnimation是一個(gè)抽象類,不能直接用于實(shí)現(xiàn)CALayer動(dòng)畫操作,但是它的類定義中增加用于設(shè)置CALayer可被實(shí)現(xiàn)動(dòng)畫的屬性keyPath,總結(jié)這些屬性如下:
| transform.rotation | 默認(rèn)圍繞z軸旋轉(zhuǎn),相當(dāng)于transform.rotation.z |
| transform.rotation.x transform.rotation.y transform.rotation.z | 分別圍繞x軸、y軸、z軸旋轉(zhuǎn); |
| transform.scale | 在所有方向上進(jìn)行縮放 |
| transform.scale.x transform.scale.y transform.scale.z | 分別在x軸、y軸、z軸方向上縮放; |
| transform.translation | 平移到指定坐標(biāo)點(diǎn) |
| transform.translation.x transform.translation.y transform.translation.z | 分別在x軸、y軸、z軸方向上平移; |
| zPosition | z軸位置 |
| opacity | 透明度 |
| backgroundColor | 背景顏色 |
| cornerRadius | 圓角大小 |
| borderWidth | 邊框?qū)挾?/td> |
| bounds | 圖層大小 |
| contents | 寄宿圖內(nèi)容 |
| contentsRect | 可視內(nèi)容 |
| position | 圖層位置,類似transform.translation |
| shadowColor | 陰影顏色 |
| shadowOffset | 陰影偏移 |
| shadowOpacity | 陰影透明度 |
| shadowRadius | 陰影角度 |
附:KeyPath官方參考鏈接
五、基礎(chǔ)動(dòng)畫CABasicAnimation
CABasicAnimation即基礎(chǔ)動(dòng)畫,在指定可動(dòng)畫屬性后,動(dòng)畫會(huì)按照預(yù)定的參數(shù)持續(xù)一定時(shí)間由初始值變換為終點(diǎn)值。其實(shí),CABasicAnimation就相當(dāng)于只有開(kāi)始和結(jié)束兩個(gè)幀的特殊關(guān)鍵幀動(dòng)畫(后續(xù)會(huì)詳解);
1.屬性說(shuō)明
| fromValue | 起始值 |
| toValue | 結(jié)束值 |
| byValue | keyPath屬性的變化值 |
2.動(dòng)畫演示
下面的示例使用CABasicAnimation實(shí)現(xiàn)了修改顏色圖層colorLayer的背景色為隨機(jī)顏色的動(dòng)畫,具體的代碼如下:
@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)建動(dòng)畫CABasicAnimation *animation = [CABasicAnimation animation];animation.keyPath = @"backgroundColor";//步驟2:設(shè)定動(dòng)畫屬性animation.autoreverses = NO;animation.duration = 0.25;animation.repeatCount = 1;animation.removedOnCompletion = NO;animation.fillMode = kCAFillModeForwards;animation.delegate = self;UIColor *randomColor = [UIColor randomColor]; //自定義獲取隨機(jī)色的方法animation.toValue = (__bridge id _Nullable)(randomColor.CGColor);//步驟3:添加動(dòng)畫到圖層[self.colorLayer addAnimation:animation forKey:@"keyPath_backgroundColor"]; }- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{//禁用隱式動(dòng)畫[CATransaction begin];[CATransaction setDisableActions:true];self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;[CATransaction commit]; }效果圖如下:
CABasicAnimation.gif
總結(jié)創(chuàng)建動(dòng)畫的兩種方式如下:
//方法1:實(shí)例化同時(shí)指定動(dòng)畫類型 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];//方法2:先實(shí)例化,再指定動(dòng)畫類型 CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"backgroundColor";3.關(guān)閉隱式動(dòng)畫
對(duì)獨(dú)立圖層(即非UIView的關(guān)聯(lián)圖層,類似上述例子中的colorLayer)做更新屬性的顯式動(dòng)畫,我們需要設(shè)置一個(gè)事務(wù)來(lái)禁用圖層行為,否則動(dòng)畫會(huì)發(fā)生兩次,一次是因?yàn)轱@式的CABasicAnimation,另一次是因?yàn)殡[式動(dòng)畫,從而導(dǎo)致我們看到的動(dòng)畫異常。
補(bǔ)充--彈簧動(dòng)畫使用
iOS 彈簧動(dòng)畫詳解CASpringAnimation
?
六、關(guān)鍵幀動(dòng)畫CAKeyframeAnimation
CACAKeyfameAnimation是CAPropertyAnimation的另一個(gè)子類,它和和CABasicAnimation一樣都只能作用于圖層對(duì)象的單一屬性;它們的區(qū)別在于:CACAKeyfameAnimation不限制于設(shè)置一個(gè)起始值和結(jié)束值,而是可以根據(jù)一連串的值來(lái)做動(dòng)畫。其實(shí),CABasicAnimation可看做是只有2個(gè)關(guān)鍵幀的CAKeyframeAnimation。
1.關(guān)鍵幀動(dòng)畫常用屬性總結(jié)
關(guān)鍵幀動(dòng)畫相對(duì)于基礎(chǔ)動(dòng)畫的具有一些獨(dú)特的屬性,我們現(xiàn)將其總結(jié)如下:
| values | 用于提供關(guān)鍵幀數(shù)據(jù)的數(shù)組,數(shù)組中每一個(gè)值都對(duì)應(yīng)一個(gè)關(guān)鍵幀屬性值; 數(shù)組中的數(shù)據(jù)類型根據(jù)動(dòng)畫類型(KeyPath)而不同; 當(dāng)使用path的時(shí)候,values的值將會(huì)被自動(dòng)忽略; |
| path | 用于提供關(guān)鍵幀數(shù)據(jù)的路徑; path與values屬性作用相同,但是兩者互斥,同時(shí)指定values和path,path會(huì)覆蓋values的效果; |
| keyTimes | ktyTimes與Values中的值具有一一對(duì)應(yīng)的關(guān)系,用于指定關(guān)鍵幀在動(dòng)畫的時(shí)間點(diǎn),取值范圍是[0,1]; 若沒(méi)有設(shè)置keyTimes,則每個(gè)關(guān)鍵幀的時(shí)間是平分動(dòng)畫總時(shí)長(zhǎng)(duration); |
| timingFunctions | 用于指定每個(gè)關(guān)鍵幀之間的動(dòng)畫緩沖效果,這類似于物體運(yùn)動(dòng)的加速度; 注意:存在幾個(gè)子路徑就應(yīng)該在此數(shù)組中傳入幾個(gè)元素; |
| calculationMode | 該屬性決定了物體在每個(gè)子路徑下是跳著走還是勻速走,跟timeFunctions屬性有點(diǎn)類似; |
| rotationMode | 設(shè)置幀動(dòng)畫是否需要按照路徑切線的方向運(yùn)動(dòng); |
2.實(shí)現(xiàn)幀動(dòng)畫:使用values
從關(guān)鍵幀動(dòng)畫的屬性可以看出,我們可以總結(jié)出關(guān)鍵幀動(dòng)畫的實(shí)現(xiàn)方式實(shí)際分為兩種:
1.通過(guò)values設(shè)置關(guān)鍵幀屬性值數(shù)組;
2.通過(guò)path設(shè)置關(guān)鍵幀路徑,而且此種方式的優(yōu)先級(jí)較高;
這里首先測(cè)試第一種方式,實(shí)現(xiàn)這樣的關(guān)鍵幀動(dòng)畫:創(chuàng)建一個(gè)紫色滑塊在四個(gè)坐標(biāo)點(diǎn)之間滑動(dòng);具體的代碼實(shí)現(xiàn)如下:
關(guān)鍵幀動(dòng)畫效果如下:
CAKeyframeAnimation_values.gif
3.實(shí)現(xiàn)關(guān)鍵幀動(dòng)畫:使用path
現(xiàn)在,我們測(cè)試CAKeyframeAnimation使用path實(shí)現(xiàn)這樣一個(gè)動(dòng)畫:一架飛機(jī)沿著一個(gè)簡(jiǎn)單的曲線運(yùn)動(dòng)飛行;具體的操作包括以下幾個(gè)步驟:
1.使用UIKit提供的UIBezierPath類創(chuàng)建貝塞爾曲線,作為飛機(jī)飛行的路線軌跡;
2.使用CAShapeLayer在屏幕上繪制曲線(此步驟對(duì)于動(dòng)畫不是必須的,只是為了動(dòng)畫看起來(lái)更直觀);
3.創(chuàng)建用于顯示飛機(jī)的視圖,將其設(shè)置在貝塞爾曲線的初始位置;
4.創(chuàng)建并執(zhí)行關(guān)鍵幀動(dòng)畫,實(shí)現(xiàn)飛機(jī)飛行的曲線動(dòng)畫;
關(guān)鍵幀動(dòng)畫效果圖如下:
CAKeyframeAnimation_path.gif
七、動(dòng)畫組CAGroupAnimation
CAGroupAnimation顧名思義,就是可以將不同的動(dòng)畫效果組合起來(lái),CABasicAnimation和CAKeyframeAnimation都僅僅作用于單一的屬性,而CAAnimationGrop可以設(shè)置其animations數(shù)組的屬性來(lái)組合別的動(dòng)畫,從而達(dá)到混合多種動(dòng)畫效果的目的;
下面演示一個(gè)動(dòng)畫組的示例:組合基礎(chǔ)動(dòng)畫和關(guān)鍵幀動(dòng)畫,實(shí)現(xiàn)一個(gè)滑塊在沿path運(yùn)動(dòng)過(guò)程修改其顏色,具體的測(cè)試代碼如下:
@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)建貝塞爾曲線,即幀動(dòng)畫運(yùn)動(dò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,便于觀察動(dòng)畫;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í)行完的動(dòng)畫,防止多重動(dòng)畫導(dǎo)致異常[self.colorView.layer removeAnimationForKey:@"groupAnimation"];//1.創(chuàng)建基礎(chǔ)動(dòng)畫:修改背景色為紫色CABasicAnimation *basicAnimation = [CABasicAnimation animation];basicAnimation.keyPath = @"backgroundColor";basicAnimation.toValue = (__bridge id _Nullable)([UIColor purpleColor].CGColor);//2.創(chuàng)建關(guān)鍵幀動(dòng)畫CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animation];keyFrameAnimation.keyPath = @"position";keyFrameAnimation.path = self.bezierPath.CGPath;keyFrameAnimation.rotationMode = kCAAnimationRotateAuto;//3.創(chuàng)建組動(dòng)畫:組合基礎(chǔ)動(dòng)畫和關(guān)鍵幀動(dòng)畫CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];groupAnimation.animations = @[basicAnimation, keyFrameAnimation];groupAnimation.duration = 4.0;[self.colorView.layer addAnimation:groupAnimation forKey:@"groupAnimation"]; }動(dòng)畫組的效果如下:
CAGroupAnimation.gif
八、過(guò)渡動(dòng)畫CATransition
1.過(guò)渡動(dòng)畫簡(jiǎn)介
屬性動(dòng)畫只能對(duì)圖層的可動(dòng)畫屬性起作用,而過(guò)渡動(dòng)畫可以改變非動(dòng)畫屬性(比如交換一段文本和圖片),或者從層級(jí)關(guān)系中添加或者移除圖層;于是就有了過(guò)渡的概念;
過(guò)渡動(dòng)畫使用CATransition來(lái)實(shí)現(xiàn),它同樣是CAAnimation的子類;它并不像屬性動(dòng)畫那樣在平滑的兩個(gè)值之間做動(dòng)畫,而是影響到整個(gè)圖層的變化。過(guò)渡動(dòng)畫首先展示之前的圖層外觀,然后通過(guò)一個(gè)交換過(guò)渡到新的外觀。
過(guò)渡動(dòng)畫通常用于刪除子控件、添加子控件、切換兩個(gè)子控件等。
2.過(guò)渡動(dòng)畫屬性介紹
過(guò)渡動(dòng)畫有type和subtype兩個(gè)關(guān)鍵屬性,type用于指定動(dòng)畫類型,subtype用于指定動(dòng)畫移動(dòng)的方向;
type屬性:
type屬性是一個(gè)NSString類型,用于控制整體動(dòng)畫效果類型,具體的可選類型如下:
| fade | 默認(rèn)效果,漸變 | kCATransitionFade | 否 |
| moveIn | 覆蓋 | kCATransitionMoveIn | 是 |
| Push | 退出 | kCATransitionPush | 是 |
| Reveal | 揭開(kāi) | kCATransitionReveal | 是 |
| cube | 立方體 | 無(wú)(私有類型) | 是 |
| suckEffect | 收縮 | 無(wú)(私有類型) | 否 |
| oglFlip | 翻轉(zhuǎn) | 無(wú)(私有類型) | 是 |
| rippleEffect | 水波動(dòng)畫 | 無(wú)(私有類型) | 否 |
| pageCurl | 頁(yè)面揭開(kāi) | 無(wú)(私有類型) | 只支持左右方向 |
| vpageUnCurl | 放下頁(yè)面 | 無(wú)(私有類型) | 只支持左右方向 |
| cameraIrisHollowOpen | 鏡頭打開(kāi) | 無(wú)(私有類型) | 否 |
| cameraIrisHollowClose | 鏡頭關(guān)閉 | 無(wú)(私有類型) | 否 |
目前為止,我們只能使用type的前四種公開(kāi)屬性,但是我們可以通過(guò)一些別的方法來(lái)自定義過(guò)渡效果(后續(xù)介紹);
subtype屬性:
subtype屬性也是一個(gè)NSString類型,用于控制動(dòng)畫方向,具體的可選類型如下:
| kCATransitionFromRight | 從右向左 |
| kCATransitionFromLeft | 從左向右 |
| kCATransitionFromTop | 從上向下 |
| kCATransitionFromBottom | 從下向上 |
3.過(guò)渡動(dòng)畫的使用
現(xiàn)在設(shè)想這樣的一個(gè)需求:修改UIImageView的image屬性,實(shí)現(xiàn)淡入淡出的平滑動(dòng)畫的效果;此時(shí)我們需要使用CATransition來(lái)對(duì)非動(dòng)畫屬性做動(dòng)畫,具體的關(guān)鍵代碼如下:
@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]; }過(guò)渡動(dòng)畫的效果如下:
CATransition.gif
注意:和屬性動(dòng)畫不同,對(duì)指定圖層一次只能使用那一次CATransition,因此無(wú)論對(duì)動(dòng)畫的鍵設(shè)置為什么值,過(guò)渡動(dòng)畫都會(huì)對(duì)它的鍵設(shè)置為”transition”,也就是常量KCATransition.
4.隱式過(guò)渡
CATransition可以對(duì)圖層任何變化平滑過(guò)渡,這使得它成為那些不好做動(dòng)畫的屬性圖層行為的理想之選。所以,蘋果將CATransition作為設(shè)置CALayer的contents屬性時(shí)的默認(rèn)行為,對(duì)圖層contents圖片做的改動(dòng)都會(huì)自動(dòng)附上淡入淡出的效果,這也就解釋了隱式動(dòng)畫的原理;
但注意:
1.對(duì)于視圖關(guān)聯(lián)的圖層,過(guò)渡動(dòng)畫的默認(rèn)效果是禁用的;
2.我們不能錯(cuò)誤的理解CATransition只可以改變非動(dòng)畫屬性,其實(shí)它也可以對(duì)類似backgroundColor的屬性做過(guò)渡效果動(dòng)畫;
5.自定義過(guò)渡動(dòng)畫
過(guò)渡動(dòng)畫的過(guò)程就是對(duì)原始圖層外觀截圖,然后添加一段動(dòng)畫,平滑過(guò)渡到圖層改變之后的那個(gè)截圖效果。如果我們知道如何對(duì)圖層截圖,我們就可以使用屬性動(dòng)畫來(lái)自定義CATransition動(dòng)畫了。
CALayer有一個(gè)-renderInContenxt:方法,通過(guò)它可以將圖層繪制到Core Graphics的上下文中捕獲當(dāng)前內(nèi)容的圖片;所以現(xiàn)在我們嘗試這樣的實(shí)現(xiàn):對(duì)當(dāng)前視圖控制器View進(jìn)行截圖,然后在改變其背景色的時(shí)候?qū)貓D快速旋轉(zhuǎn)并且淡出,以達(dá)到一種過(guò)渡的效果;具體的代碼示例如下:
- (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];//使用自定義方法得到隨機(jī)顏色(切換后的顏色)UIColor *randomColor = [UIColor randomColor];self.view.backgroundColor = randomColor;//使用UIView動(dòng)畫方法來(lái)代替屬性動(dòng)畫(為了簡(jiǎn)化代碼步驟)[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];}];}自定義過(guò)渡動(dòng)畫的效果如下:
CATransitionn_Custom.gif
注意:-renderInContext:捕獲了圖層的圖片和子圖層,但是不能對(duì)子圖層正確的處理變換效果,而且對(duì)視頻和OpenGL內(nèi)容也不起作用。但是使用CATransition,或者使用私有的截屏方式就沒(méi)有這個(gè)限制了。
九、委托模式下的動(dòng)畫區(qū)分
對(duì)于CAAnimation而言,使用委托模式而不是一個(gè)完成塊會(huì)帶來(lái)一個(gè)問(wèn)題,那就是設(shè)置多個(gè)動(dòng)畫時(shí),無(wú)法在回調(diào)方法中區(qū)分。通常視圖控制器本身會(huì)作為一個(gè)委托,但所有動(dòng)畫都會(huì)調(diào)用同一個(gè)回調(diào)方法,所以我們需要判斷到底是哪個(gè)圖層的動(dòng)畫調(diào)用;
首先,動(dòng)畫本身會(huì)作為一個(gè)參數(shù)傳入委托的方法,也許你會(huì)認(rèn)為可以在控制器中把動(dòng)畫存儲(chǔ)為一個(gè)屬性,然后在回調(diào)用比較,但實(shí)際上并不起作用,因?yàn)槲袀魅氲膭?dòng)畫參數(shù)是原始值的一個(gè)深拷貝,從而不是同一個(gè)值。最后,這里提供兩種思路來(lái)解決這個(gè)問(wèn)題:
思路1:唯一key參數(shù)
當(dāng)使用-addAnimation:forkey:添加動(dòng)畫到圖層時(shí),對(duì)每個(gè)動(dòng)畫都關(guān)聯(lián)一個(gè)唯一的鍵,這樣就可以對(duì)每個(gè)圖層循環(huán)所有鍵,然后調(diào)用animationForKey:來(lái)對(duì)比結(jié)果;
思路2:KVC(鍵-值-編碼)協(xié)議
像所有NSObject子類一樣,CAAnimation也遵循了KVC協(xié)議,就像一個(gè)NSDictionary一樣允許我們隨意設(shè)置鍵值對(duì);于是我們可以使用setValue:forKey:和-valueForKey:來(lái)存取屬性,通過(guò)為對(duì)象創(chuàng)建一個(gè)鍵值對(duì)來(lái)判斷區(qū)分動(dòng)畫;
驗(yàn)證上述兩種思路的具體的代碼使用如下:
@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{ //背景色顏色動(dòng)畫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]; //自定義獲取隨機(jī)色的方法animation1.toValue = (__bridge id _Nullable)(randomColor.CGColor);[animation1 setValue:@"animation_background" forKey:@"AnimationKey"];[self.colorView.layer addAnimation:animation1 forKey:@"key_backgroundColor"];//透明度動(dòng)畫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"]; }//動(dòng)畫結(jié)束的代理:區(qū)分動(dòng)畫- (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:KVCNSString *animationValue = [anim valueForKey:@"AnimationKey"];NSLog(@"animationValue:%@",animationValue);if([animationValue isEqualToString:@"animation_background"]){}else if([animationValue isEqualToString:@"animation_opacity"]){}}注意:使用唯一key參數(shù)這種方法,必須設(shè)置removeOnCompletion為NO,否則通過(guò)animaitonForKey:獲取的CAAnimation對(duì)象為空對(duì)象無(wú)法進(jìn)行比較。
十、虛擬屬性
屬性動(dòng)畫CAPropertyAnimation的keyPath實(shí)際上針對(duì)的是關(guān)鍵路徑而不是一個(gè)鍵,這就意味著屬性動(dòng)畫作用的對(duì)象可以子屬性(即屬性的屬性)甚至虛擬屬性;
那么什么是虛擬屬性呢?舉個(gè)例子來(lái)講,CATransform3D實(shí)際上是一個(gè)結(jié)構(gòu)體而非一個(gè)對(duì)象,所以它并不符合KVC相關(guān)屬性,但是我們卻可以使用transform.rotation來(lái)實(shí)現(xiàn)動(dòng)畫;這其實(shí)就是因?yàn)閠ransform.rotation是一個(gè)CALayer可用于處理動(dòng)畫變換的虛擬屬性;
1.虛擬屬性的作用
為了理解虛擬屬性的用處,我們現(xiàn)在考慮這樣一個(gè)動(dòng)畫:對(duì)一個(gè)物體實(shí)現(xiàn)旋轉(zhuǎn)動(dòng)畫,由于CALayer并沒(méi)有顯式的給提供角度或者方向之類的屬性,所以我們自然想到使用transform屬性來(lái)實(shí)現(xiàn)動(dòng)畫,測(cè)試代碼具體如下:
@interface TestBacicAnimation3VC ()@property(nonatomic, strong) UILabel *txtLabel;@end@implementation TestBacicAnimation3VC- (void)viewDidLoad {[super viewDidLoad];//創(chuàng)建測(cè)試虛擬屬性的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 = @"測(cè)試虛擬屬性";[self.view addSubview:_txtLabel]; }- (IBAction)startAnimation:(UIButton *)sender{//步驟1:創(chuàng)建動(dòng)畫CABasicAnimation *animation = [CABasicAnimation animation];animation.keyPath = @"transform”; //代碼1//步驟2:設(shè)定動(dòng)畫屬性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];}在此例中,我們把旋轉(zhuǎn)角度從M_PI(180度)調(diào)整到M_PI*2(360度),對(duì)比兩次動(dòng)畫會(huì)發(fā)現(xiàn),txtLabel完全看不到旋轉(zhuǎn)的動(dòng)畫效果;這是因?yàn)镃ATransform3D矩陣做了360度旋轉(zhuǎn)其實(shí)適合0度是一樣的,所以最后的值根本就沒(méi)變;
這里就需要用到上述說(shuō)到的虛擬屬性了,為了旋轉(zhuǎn)圖層,我們可以針對(duì)于transform.rotation關(guān)鍵路徑應(yīng)用動(dòng)畫,而不是transform本身;現(xiàn)在將對(duì)上述代碼進(jì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);再來(lái)看動(dòng)畫的效果如下:
CABasicAnimation_VirtualProperty.gif
總結(jié)transform.rotation相比transfrom做動(dòng)畫的好處如下:
2.虛擬屬性原理
我們已經(jīng)說(shuō)過(guò)CATransform3D是一個(gè)結(jié)構(gòu)體而非一個(gè)對(duì)象,所以transfrom.rotation其實(shí)是不存在的,我們不可以直接設(shè)置transform.rotation或者transform.scale;
實(shí)際上,Core Animation是自動(dòng)通過(guò)CAValueFunction計(jì)算的值來(lái)更新transform屬性的,CAValueFunction將我們賦值虛擬屬性transfom.rotation的浮點(diǎn)值轉(zhuǎn)換成了真正能用于擺放圖層的CATransform3D矩陣值;我們也可以通過(guò)設(shè)置CAPropertyAnimation的valuefunction屬性來(lái)改變,這樣我們自定義函數(shù)就會(huì)覆蓋默認(rèn)函數(shù)。
CAValueFuncation對(duì)于那些不能簡(jiǎn)單相加的屬性(例如變換矩陣)做動(dòng)畫十分有用,但是此方法的實(shí)現(xiàn)細(xì)節(jié)是私有的,所以,目前我們并不能通過(guò)繼承來(lái)自定義此方法;我們可以通過(guò)使用蘋果已經(jīng)提供的常量來(lái)改善動(dòng)畫(目前都是和變換矩陣的虛擬屬性相關(guān),所以沒(méi)太多的應(yīng)用場(chǎng)景了,因?yàn)檫@些屬性都有了默認(rèn)的實(shí)現(xiàn)方式)。
十一、在動(dòng)畫過(guò)程中取消動(dòng)畫
在使用動(dòng)畫的過(guò)程中,我們可能需要適時(shí)的移除不要的動(dòng)畫,否則就可能造成內(nèi)存的泄漏問(wèn)題;從圖層中取消動(dòng)畫的方法有以下兩種方式:
//方法1:取消指定動(dòng)畫 /* Remove any animation attached to the layer for 'key'. */ - (void)removeAnimationForKey:(NSString *)key;//方法2:移除所有動(dòng)畫 /* Remove all animations attached to the layer. */ - (void)removeAllAnimations;關(guān)于移除動(dòng)畫的幾點(diǎn)說(shuō)明:
1.動(dòng)畫一旦被移除,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值;
2.動(dòng)畫通常默認(rèn)結(jié)束之后被自動(dòng)移除,除非設(shè)置了removeCompletion為NO;
3.動(dòng)畫若設(shè)置為結(jié)束之后不自動(dòng)移除,那么我們?cè)诓恍枰臅r(shí)候需手動(dòng)移除,否則它會(huì)一直在內(nèi)存中,直到圖層被銷毀;
測(cè)試取消動(dòng)畫效果圖如下:
CAAnimation_cancel.gif
代碼分析:
-animationDidStop:finished:方法中的flag參數(shù)表明了動(dòng)畫是自然結(jié)束還是被打斷的;此例中通過(guò)停止按鈕來(lái)終止動(dòng)畫會(huì)打印NO,自然完成動(dòng)畫時(shí)打印YES;
總結(jié)
以上是生活随笔為你收集整理的iOS动画-CAAnimation使用详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MCR和MRC汇编指令
- 下一篇: 生成对抗网络——GAN(一)