iOS Core Animation 簡明系列教程

斯人如是丶發表於2016-05-11

原文:http://blog.csdn.net/lvxiangan/article/details/17167827#t2


iOS Core Animation 簡明系列教程 

看到無數的CA教程,都非常的難懂,各種事務各種圖層關係看的人頭大。自己就想用通俗的語言翻譯給大家聽,儘可能準確表達,如果哪裡有問題,請您指出我會盡快修改。

1.什麼是Core Animation?

  • 它是一套包含圖形繪製,投影,動畫的OC類集合。它就是一個framework。通過CoreAnimation提供的介面,你可以方便完成自己所想要的動畫。

1

2.我眼中的Core Animation?

動畫和拍電影一樣,而我們就如同導演一樣,全權負責這場小電影(:D)。

電影中有3類元素,演員,劇本,拍片。動畫也一樣,Layer,CAAnimation,addAnimation。

其中類比關係為

  • CALayer : 演員
  • CAAnimation :劇本
  • addAnimation :拍片

舉一個簡單例子,我想導演異常楊冪(Layer)從花園蹦蹦跳跳移動到臥室的電影,過程分為

  • 1.演員(楊冪)
  • 2.劇本(從花園蹦蹦跳跳移動到臥室)
  • 3.拍片(把演員、劇本組合起來)。

同樣我想把一個Layer從A點移動到B點,同時按照X軸旋轉並放大,過程為

  • 1.Layer(kkLayer)
  • 2.CAAnimation(這裡我會建一個動畫組,動畫組是什麼先別急等會講解),
  • 3.addAnimation(讓動畫開始)。

3.什麼是CALayer,CAAnimation ?

3.1CALayer

  • Layer Classes是core animation的基礎。Layer Classes提供了一個抽象的概念,這個概念對於那些使用NSview和UIview的開發者來說是很熟悉的。基礎層是由CAlayer類提供的,CAlayer是所有Core Animation層的父類。
  • 同一個檢視類的例項一樣,一個CAlayer例項也有一個單獨的superlayer和上面所有的子層(sublayers),它建立了一個有層次結構的層,我們稱之為layer tree。layers的繪製就像views一樣是從後向前繪製的,繪製的時候我們要指定其相對與他們的superlayer的集合形狀,同時還需要建立一個區域性的座標系。layers可以做一些更復雜的操作,例如rotate(旋轉),skew(傾斜),scale(放縮),和project the layer content(層的投影)。

巴拉巴拉那麼多,其實CALayer是個簡單的類,它是用來在螢幕上顯示內容展示的矩形區域。那麼和UIView區別是什麼?

  • 1.每個UIView 都有 CALayer,即 UIView.layer或者[UIView layer]
  • 2.UIView可以響應事件,而CALayer只負責顯示。(UIView是有delegate的CALayer)

關於CAlayer的一些引數請移步![http://www.cnblogs.com/xunziji/archive/2012/10/30/2746769.html]

3.2 CAAnimation

CAAnimation分為這4種,他們分別是

  • 1.CABasicAnimation
  • 通過設定起始點,終點,時間,動畫會沿著你這設定點進行移動。
  • 2.CAKeyframeAnimation
  • Keyframe顧名思義就是關鍵點的frame,你可以通過設定CALayer的始點、中間關鍵點、終點的frame,時間,動畫會沿你設定的軌跡進行移動
  • 3.CAAnimationGroup
  • Group也就是組合的意思,就是把對這個Layer的所有動畫都組合起來。PS:一個layer設定了很多動畫,他們都會同時執行,如何按順序執行我到時候再講。
  • 4.CATransition
  • 這個就是蘋果幫開發者封裝好的一些動畫。

4 CABasicAnimation講解

現在進入CABasicAnimation講解,通過實現文章開頭的例子我們進行Learn by doing。 我們和剛才一樣分為3部分

4.1Layer(kkLayer)

首先初始化CALayer, 在初始化之前我們需要匯入#import <QuartzCore/QuartzCore.h>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//初始化
CALayer *kkLayer = [[CALayer alloc]init];
kkLayer.backgroundColor = [[UIColor grayColor]CGColor];
kkLayer.frame = CGRectMake(10104040);
// 設定它的frame
kkLayer.cornerRadius = 5;// 圓角處理
[self.view.layer addSublayer:kkLayer]// 增加到UIView的layer上面
// 移動kkLayer的position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:kkLayer.position];
CGPoint toPoint = kkLayer.position; toPoint.x += 180;
animation.toValue = [NSValue valueWithCGPoint:toPoint];
// 以x軸進行旋轉
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"]; rotateAnimation.fromValue = [NSNumber numberWithFloat:0.0];
rotateAnimation.toValue = [NSNumber numberWithFloat:6.0 * M_PI];
// 對kkLayer進行放大縮小
CABasicAnimation *scaoleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"]; scaoleAnimation.duration = 3;
scaoleAnimation.autoreverses = YES;
scaoleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaoleAnimation.toValue = [NSNumber numberWithFloat:2.5];
scaoleAnimation.fillMode = kCAFillModeForwards;

一些具體的說明:

1.CALayer 只能使用CGColor 具體請移步http://www.cnblogs.com/smileEvday/archive/2012/06/05/uicolor_cicolor_cgcolor.html

2.CALayer上面加入圖片可以通過 kkLayer.contents = (id)imageTmp.CGImage

3.都有哪些animationWithKeyPath,可以看圖
2

4.2 CAAnimationGroup

1
2
3
4
5
6
7
8
// 把上面的動畫組合起來
CAAnimationGroup *group = [CAAnimationGroup animation];
group.autoreverses = YES;
// 完成後反向完成
group.duration = 3.0;
group.animations = [NSArray arrayWithObjects:animation,rotateAnimation, scaoleAnimation, nil]; group.repeatCount = NSNotFound;
// PS:動畫結束以後,他會返回到自己原來的frame,如果不想到原來frame我們需要設定
group.fillMode = kCAFillModeForwards;

4.3 addAnimation(讓動畫開始)。

1
[kkLayer addAnimation:group forKey:@"kkLayerMove"];

4.4 具體效果

3

5.總結

很簡單吧,是不是感覺CA也不過如此麼。學會類比會幫助我們更好的理解事物。下一篇我們一起學習 CAKeyframeAnimation。

程式碼地址:

https://github.com/Coneboy-k/KKCoreAnimation

本文版權歸Coneboy.com所有


零.前言

這裡沒有太多的程式碼細節,只是探索iOS動畫的基本概念,以及其抽象模型,數學基礎等.我們學習一個知識的時候一般有兩個部分,抽象部分和形象部分,抽象好比語言的語法,是規則,形象好比具體的句子,可以用來和別人交流的.抽象比形象難於理解,但比形象通用.其實數學中經常碰到抽象和形象的概念,比如有一系列離散的點,這是形象;通過這些點我們擬合出一條曲線,得到其函式,函式是抽象的;然後通過這個函式我們可以得到更多的點,這又回到了形象上.所以學習任何知識不能僅僅停留在會用了,而要上升一個層次,去學習研究其抽象層次上的知識,抽象層度越高,則越通用.

一.基本概念

什麼是Animation(動畫),簡單點說就是在一段時間內,顯示的內容發生了變化.對CALayer來說就是在一段時間內,其Animatable Property發生了變化.從CALayer(CA = Core Animation)類名來看就可以看出iOS的Layer就是為動畫而生的,便於實現良好的互動體驗. 這裡涉及到兩個東西: 一是Layer(基類CALayer),一是Animation(基於CAAnimation). Animation作用於Layer.CALayer提供了介面用於給自己新增Animation. 用於顯示的Layer本質上講是一個Model,包含了Layer的各種屬性值. Animation則包含了動畫的時間,變化,以及變化的速度.下面分別詳細講解Layer和Animation相關知識.

二.CALayer及時間模型

我們都知道UIView是MVC中的View.UIView的職責在於介面的顯示和介面事件的處理.每一個View的背後都有一個layer(可以通過view.layer進行訪問),layer是用於介面顯示的.CALayer屬於QuartzCore框架,非常重要,但並沒有想象中的那麼好理解.我們通常操作的用於顯示的Layer在Core Animation這層的概念中其實擔當的是資料模型Model的角色,它並不直接做渲染的工作.關於Layer,之前從座標系的角度分析過,這次則側重於它的時間系統.

1.Layer的渲染架構

Layer也和View一樣存在著一個層級樹狀結構,稱之為圖層樹(Layer Tree),直接建立的或者通過UIView獲得的(view.layer)用於顯示的圖層樹,稱之為模型樹(Model Tree),模型樹的背後還存在兩份圖層樹的拷貝,一個是呈現樹(Presentation Tree),一個是渲染樹(Render Tree). 呈現樹可以通過普通layer(其實就是模型樹)的layer.presentationLayer獲得,而模型樹則可以通過modelLayer屬性獲得(詳情文件).模型樹的屬性在其被修改的時候就變成了新的值,這個是可以用程式碼直接操控的部分;呈現樹的屬性值和動畫執行過程中介面上看到的是一致的.而渲染樹是私有的,你無法訪問到,渲染樹是對呈現樹的資料進行渲染,為了不阻塞主執行緒,渲染的過程是在單獨的程式或執行緒中進行的,所以你會發現Animation的動畫並不會阻塞主執行緒.

2.事務管理

CALayer的那些可用於動畫的(Animatable)屬性,稱之為Animatable Properties,這裡有一份詳情的列表,羅列了所有的 CALayer Animatable Properties. 如果一個Layer物件存在對應著的View,則稱這個Layer是一個Root Layer,非Root Layer一般都是通過CALayer或其子類直接建立的.下面的subLayer就是一個典型的非Root Layer,它沒有對應的View物件關聯著.

    subLayer = [[CALayer alloc] init];
    subLayer.frame = CGRectMake(0, 0, 300, 300);
    subLayer.backgroundColor = [[UIColor redColor] CGColor];
    [self.view.layer addSublayer:subLayer];

所有的非Root Layer在設定Animatable Properties的時候都存在著隱式動畫,預設的duration是0.25秒.

    subLayer.position = CGPointMake(300,400);

像上面這段程式碼當下一個RunLoop開始的時候並不是直接將subLayer的position變成(300,400)的,而是有個移動的動畫進行過渡完成的.

任何Layer的animatable屬性的設定都應該屬於某個CA事務(CATransaction),事務的作用是為了保證多個animatable屬性的變化同時進行,不管是同一個layer還是不同的layer之間的.CATransaction也分兩類,顯式的和隱式的,當在某次RunLoop中設定一個animatable屬性的時候,如果發現當前沒有事務,則會自動建立一個CA事務,線上程的下個RunLoop開始時自動commit這個事務,如果在沒有RunLoop的地方設定layer的animatable屬性,則必須使用顯式的事務.

顯式事務的使用如下:

[CATransaction begin];
...  
[CATransaction commit];

事務可以巢狀.當事務巢狀時候,只有當最外層的事務commit了之後,整個動畫才開始.

可以通過CATransaction來設定一個事務級別的動畫屬性,覆蓋隱式動畫的相關屬性,比如覆蓋隱式動畫的duration,timingFunction.如果是顯式動畫沒有設定duration或者timingFunction,那麼CA事務設定的這些引數也會對這個顯式動畫起作用.

還可以設定completionBlock,噹噹前CATransaction的所有動畫執行結束後, completionBlock會被呼叫.

3.時間系統

CALayer實現了CAMediaTiming協議. CALayer通過CAMediaTiming協議實現了一個有層級關係的時間系統.除了CALayer,CAAnimation也採納了此協議,用來實現動畫的時間系統. 
在CA中,有一個Absolute Time(絕對時間)的概念,可以通過CACurrentMediaTime()獲得,其實這個絕對時間就是將mach_absolute_time()轉換成秒後的值.這個時間和系統的uptime有關,系統重啟後CACurrentMediaTime()會被重置. 
就和座標存在相對座標一樣,不同的實現了CAMediaTiming協議的存在層級關係的物件也存在相對時間,經常需要進行時間的轉換,CALayer提供了兩個時間轉換的方法:

- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;

現在來重點研究CAMediaTiming協議中幾個重要的屬性.

beginTime

無論是圖層還是動畫,都有一個時間線Timeline的概念,他們的beginTime是相對於父級物件的開始時間. 雖然蘋果的文件中沒有指明,但是通過程式碼測試可以發現,預設情況下所有的CALayer圖層的時間線都是一致的,他們的beginTime都是0,絕對時間轉換到當前Layer中的時間大小就是絕對時間的大小.所以對於圖層而言,雖然建立有先後,但是他們的時間線都是一致的(只要不主動去修改某個圖層的beginTime),所以我們可以想象成所有的圖層預設都是從系統重啟後開始了他們的時間線的計時.

但是動畫的時間線的情況就不同了,當一個動畫建立好,被加入到某個Layer的時候,會先被拷貝一份出來用於加入當前的圖層,在CA事務被提交的時候,如果圖層中的動畫的beginTime為0,則beginTime會被設定為當前圖層的當前時間,使得動畫立即開始.如果你想某個直接加入圖層的動畫稍後執行,可以通過手動設定這個動畫的beginTime,但需要注意的是這個beginTime需要為 CACurrentMediaTime()+延遲的秒數,因為beginTime是指其父級物件的時間線上的某個時間,這個時候動畫的父級物件為加入的這個圖層,圖層當前的時間其實為[layer convertTime:CACurrentMediaTime() fromLayer:nil],其實就等於CACurrentMediaTime(),那麼再在這個layer的時間線上往後延遲一定的秒數便得到上面的那個結果.

timeOffset

這個timeOffset可能是這幾個屬性中比較難理解的一個,官方的文件也沒有講的很清楚. local time也分成兩種一種是active local time 一種是basic local time.
timeOffset則是active local time的偏移量. 
你將一個動畫看作一個環,timeOffset改變的其實是動畫在環內的起點,比如一個duration為5秒的動畫,將timeOffset設定為2(或者7,模5為2),那麼動畫的執行則是從原來的2秒開始到5秒,接著再0秒到2秒,完成一次動畫.

speed

speed屬性用於設定當前物件的時間流相對於父級物件時間流的流逝速度,比如一個動畫beginTime是0,但是speed是2,那麼這個動畫的1秒處相當於父級物件時間流中的2秒處. speed越大則說明時間流逝速度越快,那動畫也就越快.比如一個speed為2的layer其所有的父輩的speed都是1,它有一個subLayer,speed也為2,那麼一個8秒的動畫在這個執行於這個subLayer只需2秒(8 / (2 * 2)).所以speed有疊加的效果.

fillMode

fillMode的作用就是決定當前物件過了非active時間段的行為. 比如動畫開始之前,動畫結束之後。如果是一個動畫CAAnimation,則需要將其removedOnCompletion設定為NO,要不然fillMode不起作用. 下面來講各個fillMode的意義 
kCAFillModeRemoved 這個是預設值,也就是說當動畫開始前和動畫結束後,動畫對layer都沒有影響,動畫結束後,layer會恢復到之前的狀態 
kCAFillModeForwards 當動畫結束後,layer會一直保持著動畫最後的狀態 
kCAFillModeBackwards 這個和kCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便立即進入動畫的初始狀態並等待動畫開始.你可以這樣設定測試程式碼,將一個動畫加入一個layer的時候延遲5秒執行.然後就會發現在動畫沒有開始的時候,只要動畫被加入了layer,layer便處於動畫初始狀態 
kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入後開始之前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態.

其他的一些引數都是比較容易理解的.

實際應用

參見蘋果官方 QA1673 How to pause the animation of a layer tree

-(void)pauseLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

-(void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

三.顯式動畫Animation

當需要對非Root Layer進行動畫或者需要對動畫做更多自定義的行為的時候,就必須使用到顯式動畫了,顯式動畫的基類為CAAnimation,常用的是CABasicAnimation,CAKeyframeAnimation有時候還會使用到CAAnimationGroup,CATransition(注意不是CATransaction,Transition是過渡的意思). 

這裡再強調關於動畫的兩個重要的點:一是中間狀態的插值計算(Interpolation),二是動畫節奏控制(Timing); 有時候插值計算也和Timing有一定關係. 如果狀態是一維空間的值(比如透明度),那麼插值計算的結果必然再起點值和終點值之間,如果狀態是二維空間的值(比如position),那麼一般情況下插值得到的點會落在起點和終點之間的線段上(當然也有可能連線是圓滑曲線).

1.CABasicAnimation

不管是CABasicAnimation還是CAKeyframeAnimation都是繼承於CAPropertyAnimation.  CABasicAnimation有三個比較重要的屬性,fromValue,toValue,byValue,這三個屬性都是可選的,但不能同時多於兩個為非空.最終都是為了確定animation變化的起點和終點.Setting Interpolation Values詳細介紹了這個三個值的各種情況以及用途. 設定了動畫的起點和終點之後,中間的值都是通過插值方式計算出來的.插值計算的結果由timingFunction指定,預設timingFunction為nil,會使用liner的,也就是變化是均勻的.

2.Timing Function的作用

Timing Function的會被用於變化起點和終點之間的插值計算.形象點說是Timing Function決定了動畫執行的節奏(Pacing),比如是均勻變化(相同時間變化量相同),先快後慢,先慢後快還是先慢再快再慢.

時間函式是使用的一段函式來描述的,橫座標是時間t取值範圍是0.0-1.0,縱座標是變化量x(t)也是取值範圍也是0.0-1.0 假設有一個動畫,duration是8秒,變化值的起點是a終點是b(假設是透明度),那麼在4秒處的值是多少呢? 可以通過計算為 a + x(4/8) * (b-a), 為什麼這麼計算呢?講實現的時間對映到單位值的時候4秒相對於總時間8秒就是0.5然後可以得到0.5的時候單位變化量是 x(0.5), x(0.5)/1 = 實際變化量/(b-a), 其中b-a為總變化量,所以實際變化量就是x(0.5) * (b-a) ,最後4秒時的值就是 a + x(0.5) * (b-a),所以計算的本質是對映.

Timing Function對應的類是CAMediaTimingFunction,它提供了兩種獲得時間函式的方式,一種是使用預定義的五種時間函式,一種是通過給點兩個控制點得到一個時間函式. 相關的方法為

+ (id)functionWithName:(NSString *)name;

+ (id)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

- (id)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

五種預定義的時間函式名字的常量變數分別為 
kCAMediaTimingFunctionLinear, 
kCAMediaTimingFunctionEaseIn, 
kCAMediaTimingFunctionEaseOut, 
kCAMediaTimingFunctionEaseInEaseOut, 
kCAMediaTimingFunctionDefault. 
下圖展示了前面四種Timing Function的曲線圖,橫座標表示時間,縱座標表示變化量,這點需要搞清楚(並不是平面座標系中xy). 
自定義的Timing Function的函式影象就是一條三次貝塞爾曲線Cubic Bezier Curve,貝塞爾曲線的優點就是光滑,用在這裡就使得變化顯得光滑.一條三次貝塞爾曲線可以由起點終點以及兩個控制點決定. 
上面的kCAMediaTimingFunctionDefault對應的函式曲線其實就是通過[(0.0,0.0), (0.25,0.1), (0.25,0.1), (1.0,1.0)]這四個點決定的三次貝塞爾曲線,頭尾為起點和終點,中間的兩個點是控制點. 
 
上圖中P0是起點,P3是終點,P1和P2是兩個控制點

如果時間變化曲線既不是直線也不是貝塞爾曲線,而是自定義的,又或者某個圖層運動的軌跡不是直線而是一個曲線,這些是基本動畫無法做到的,所以引入下面的內容,CAKeyframeAnimation,也即所謂的關鍵幀動畫.

3.CAKeyframeAnimation

任何動畫要表現出運動或者變化,至少需要兩個不同的關鍵狀態,而中間的狀態的變化可以通過插值計算完成,從而形成補間動畫,表示關鍵狀態的幀叫做關鍵幀. CABasicAnimation其實可以看作一種特殊的關鍵幀動畫,只有頭尾兩個關鍵幀.CAKeyframeAnimation則可以支援任意多個關鍵幀,關鍵幀有兩種方式來指定,使用path或者使用values,path是一個CGPathRef的值,且path只能對CALayer的 anchorPoint 和 position 屬性起作用,且設定了path之後values就不再起效了.而values則更加靈活. keyTimes這個可選引數可以為對應的關鍵幀指定對應的時間點,其取值範圍為0到1.0,keyTimes中的每一個時間值都對應values中的每一幀.當keyTimes沒有設定的時候,各個關鍵幀的時間是平分的. 
還可以通過設定可選引數timingFunctions(CAKeyframeAnimation中timingFunction是無效的)為關鍵幀之間的過渡設定timingFunction,如果values有n個元素,那麼timingFunctions則應該有n-1個.但很多時候並不需要timingFunctions,因為已經設定了夠多的關鍵幀了,比如沒1/60秒就設定了一個關鍵幀,那麼幀率將達到60FPS,完全不需要相鄰兩幀的過渡效果(當然也有可能某兩幀 值相距較大,可以使用均勻變化或者增加幀率,比如每0.01秒設定一個關鍵幀).

在關鍵幀動畫中還有一個非常重要的引數,那便是calculationMode,計算模式.其主要針對的是每一幀的內容為一個座標點的情況,也就是對anchorPoint 和 position 進行的動畫.當在平面座標系中有多個離散的點的時候,可以是離散的,也可以直線相連後進行插值計算,也可以使用圓滑的曲線將他們相連後進行插值計算. calculationMode目前提供如下幾種模式 kCAAnimationLinear 
kCAAnimationDiscrete 
kCAAnimationPaced 
kCAAnimationCubic 
kCAAnimationCubicPaced

kCAAnimationLinear calculationMode的預設值,表示當關鍵幀為座標點的時候,關鍵幀之間直接直線相連進行插值計算; 
kCAAnimationDiscrete 離散的,就是不進行插值計算,所有關鍵幀直接逐個進行顯示; 
kCAAnimationPaced 使得動畫均勻進行,而不是按keyTimes設定的或者按關鍵幀平分時間,此時keyTimes和timingFunctions無效; 
kCAAnimationCubic 對關鍵幀為座標點的關鍵幀進行圓滑曲線相連後插值計算,對於曲線的形狀還可以通過tensionValues,continuityValues,biasValues來進行調整自定義,這裡的數學原理是Kochanek–Bartels spline,這裡的主要目的是使得執行的軌跡變得圓滑; 
kCAAnimationCubicPaced 看這個名字就知道和kCAAnimationCubic有一定聯絡,其實就是在kCAAnimationCubic的基礎上使得動畫執行變得均勻,就是系統時間內運動的距離相同,此時keyTimes以及timingFunctions也是無效的.


最後推薦下WWDC 2010和2011上的關於Animation相關的Session,大家可以找找來看.2010的有說到Core Graphic相關內容.以及他們都從效能方面對CA做了些詮釋.

http://geeklu.com/2012/09/animation-in-ios/

相關文章