話外:之前自己做的一個demo帖池,用到了CABasicAnimation,覺得動畫太神奇了,現(xiàn)在才開
始認(rèn)真學(xué)習(xí)Core Animation颁股!這篇文章只寫了目前自己需要掌握的部分锋爪。
原博地址:CALayer與iOS動畫 講解及使用
1.關(guān)于Core Animation
CoreAnimation是蘋果提供的一套基于繪圖的動畫框架组题,下圖是官方文檔中給出的體系結(jié)構(gòu)。
從圖中可以看出膳沽,最底層是圖形硬件(GPU);上層是OpenGL和CoreGraphics让禀,提供一些接口來訪問GPU挑社;再上層的CoreAnimation在此基礎(chǔ)上封裝了一套動畫的API。最上面的UIKit屬于應(yīng)用層巡揍,處理與用戶的交互痛阻。所以,學(xué)習(xí)CoreAnimation也會涉及一些圖形學(xué)的知識腮敌,了解這些有助于我們更順手的使用以及更高效的解決問題阱当。
2.初識CALayer
CoreAnimation屬于QuartzCore框架俏扩,Quartz原本是macOS的Darwin核心之上的繪圖技術(shù)。在iOS中弊添,我們所看到的視圖UIView是通過QuartzCore中的CALayer顯示出來的录淡,我們討論的動畫效果也是加在這個CALayer上的。
- 下面主要的內(nèi)容是:
CALayer(圖層類)和CAAnimation(動畫類)的內(nèi)容和關(guān)系
以及他們實(shí)現(xiàn)的一個重要協(xié)議CAMediaTiming
CALayer圖層類是CoreAnimation的基礎(chǔ)油坝,它提供了一套抽象概念嫉戚。CALayer是整個圖層類的基礎(chǔ),它是所有核心動畫圖層的父類澈圈。
1.CALayer
為什么UIView要加一層Layer來負(fù)責(zé)顯示呢彬檀?我們知道QuartzCore是跨iOS和macOS平臺的,而UIView屬于UIKit是iOS開發(fā)使用的瞬女,在macOS中對應(yīng)AppKit里的NSView窍帝。這是因?yàn)閙acOS是基于鼠標(biāo)指針操作的系統(tǒng),與iOS的多點(diǎn)觸控有本質(zhì)的區(qū)別拆魏。雖然iOS在交互上與macOS有所不同盯桦,但在顯示層面卻可以使用同一套技術(shù)。
每一個UIView都有個屬性layer渤刃、默認(rèn)為CALayer類型拥峦,也可以使用自定義的Layer
/* view的leyer,view是layer的代理 */
@property(nonatomic,readonly,strong) CALayer *layer;
可以想象我們看到的View其實(shí)都是它的layer卖子,下面我們通過CALayer中的集合相關(guān)的屬性來認(rèn)識它:
bounds:圖層的bounds是一個CGRect的值略号,指定圖層的大小(bounds.size)和原點(diǎn)(bounds.origin)
position:指定圖層的位置(相對于父圖層而言)
anchorPoint:錨點(diǎn)指定了position在當(dāng)前圖層中的位置洋闽,坐標(biāo)范圍0~1玄柠。position點(diǎn)的值是相對于父圖層的,而這個position到底位于當(dāng)前圖層的什么地方诫舅,是由錨點(diǎn)決定的羽利。(默認(rèn)在圖層的中心,即錨點(diǎn)為(0.5,0.5) )刊懈。
劃重點(diǎn):設(shè)置完錨點(diǎn)后这弧,都要在設(shè)置一下position的值transform:指定圖層的幾何變換,類型為上篇說過的CATransform3D
這些屬性的注釋最后都有一句Animatable虚汛,就是說我們可以通過改變這些屬性來實(shí)現(xiàn)動畫匾浪。默認(rèn)地,我們修改這些屬性都會導(dǎo)致圖層從舊值動畫顯示為新值卷哩,稱為隱式動畫蛋辈。
注意到frame的注釋里面是沒有Animatable的。事實(shí)上将谊,我們可以理解為圖層的frame并不是一個真實(shí)的屬性:當(dāng)我們讀取frame時冷溶,會根據(jù)圖層position渐白、bounds、anchorPoint和transform的值計算出它的frame挂洛;而當(dāng)我們設(shè)置frame時礼预,圖層會根據(jù)anchorPoint改變position和bounds。也就是說frame本身并沒有被保存虏劲。
圖層不但給自己提供可視化的內(nèi)容和管理動畫托酸,而且充當(dāng)了其他圖層的容器類,構(gòu)建圖層層次結(jié)構(gòu)
圖層樹類似于UIView的層次結(jié)構(gòu)柒巫,一個view實(shí)例擁有父視圖(superView)和子視圖(subView)励堡;同樣一個layer也有父圖層(superLayer)和子圖層(subLayer)。我們可以直接在view的layer上添加子layer達(dá)到一些顯示效果堡掏,但這些單獨(dú)的layer無法像UIView那樣進(jìn)行交互響應(yīng)应结。
2.CAMediaTiming
CAMediaTiming是CoreAnimation中一個非常重要的協(xié)議,CALayer和CAAnimation都實(shí)現(xiàn)了它來對時間進(jìn)行管理泉唁。
協(xié)議定義了8個屬性鹅龄,通過它們來控制時間,這些屬性大都見名知意:
@protocol CAMediaTiming
/* 開始的時間*/
@property CFTimeInterval beginTime;
/* 持續(xù)時間*/
@property CFTimeInterval duration;
@proterty float speed;
/* timeOffset時間的偏移量亭畜,用它可以實(shí)現(xiàn)動畫的暫停扮休、繼續(xù)等效果*/
@proterty CFTimeInterval timeOffset;
/* 重復(fù)次數(shù)*/
@property float repeatCount;
@property CFTimeInterval repeatDuration;
/* autoreverses為true時時間結(jié)束后會原路返回,默認(rèn)為false */
@property BOOL autoreverses;
/* fillMode填充模式拴鸵,有4種玷坠,見下 */
@property(copy) NSString *fillMode;
@end
3.UIView動畫
UIView提供了一系列UIViewAnimationWithBlocks,我們只需要把改變可動畫屬性的代碼放在animations的block中即可實(shí)現(xiàn)動畫效果劲藐,比如:
[UIView animateWithDuration:1 animations:^(void){
if (_testView.bounds.size.width > 150)
{
_testView.bounds = CGRectMake(0, 0, 100, 100);
}
else
{
_testView.bounds = CGRectMake(0, 0, 200, 200);
}
} completion:^(BOOL finished){
NSLog(@"%d",finished);
}];
其中 我自己有用到這個方法: 彈性動畫(彈簧)
/* Performs `animations` using a timing curve described by the motion of a spring. When `dampingRatio` is 1, the
animation will smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will oscillate
more and more before coming to a complete stop. You can use the initial spring velocity to specify how fast the object at the
end of the simulated spring was moving before it was attached. It's a unit coordinate system, where 1 is defined as
travelling the total animation distance in a second. So if you're changing an object's position by 200pt in this animation, and
you want the animation to behave as if the object was moving at 100pt/s before the animation started, you'd pass 0.5. You'll
typically want to pass 0 for the velocity.
使用彈簧運(yùn)動描述的時間曲線來執(zhí)行“動畫”八堡。當(dāng)“dampingRatio”為1時,動畫將平穩(wěn)地減速到最終的模型值聘芜,而不會出現(xiàn)振蕩兄渺。阻尼比小于1會在完全停止前不斷振蕩。您可以
使用初始的彈簧速度來指定模擬彈簧的末端物體在被連接之前的移動速度汰现。它是一個單位坐標(biāo)系挂谍,其中1被定義為在一秒鐘內(nèi)移
動整個動畫距離。如果你在這個動畫中把一個對象的位置改變了200pt服鹅,你想讓動畫在動畫開始前以100pt/s的速度運(yùn)行凳兵,你就會
通過0百新。5企软。你通常要通過0來表示速度。*/
[UIView animateWithDuration:1.0 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0.8 options:UIViewAnimationOptionCurveEaseOut animations:^{
// 添加發(fā)布按鈕
[self addPubButton];
} completion:nil];
4.展示層(presentationLayer)和模型層(modelLayer)
我們知道UIView動畫其實(shí)是layer層做的饭望,而view是對layer的一層封裝仗哨,我們對view的bounds等這些屬性的操作其實(shí)都是對它所持有的layer進(jìn)行操作形庭,我們做一個簡單的實(shí)驗(yàn)—在UIView動畫的block中改變view的bounds后,分別查看下view和layer的bounds的實(shí)際值:
_testView.bounds = CGRectMake(0, 0, 100, 100);
[UIView animateWithDuration:1 animations:^(void){
_testView.bounds = CGRectMake(0, 0, 200, 200);
} completion:nil];
賦值完成后我們分別打印view厌漂,layer的bounds:
(lldb) po _textView.bounds
(origin = (x = 0, y = 0), size = (width = 200, height = 200))
(lldb) po _textView.layer.bounds
(origin = (x = 0, y = 0), size = (width = 200, height = 200))
都已經(jīng)變成了(200,200)萨醒,這是肯定的,之前已經(jīng)驗(yàn)證過set view的bounds實(shí)際上就是set 它的layer的bounds苇倡「恢剑可動畫不是layer實(shí)現(xiàn)的么?layer也已經(jīng)到達(dá)終點(diǎn)了旨椒,它是怎么將動畫展示出來的呢晓褪?
這里就要提到CALayer的兩個實(shí)例方法presentationLayer和modelLayer:
@interface CALayer : NSObject <NSCoding, CAMediaTiming>
/* 以下參考官方api注釋 */
/* presentationLayer
* 返回一個layer的拷貝,如果有任何活動動畫時综慎,包含當(dāng)前狀態(tài)的所有l(wèi)ayer屬性
* 實(shí)際上是逼近當(dāng)前狀態(tài)的近似值涣仿。
* 嘗試以任何方式修改返回的結(jié)果都是未定義的。
* 返回值的sublayers 示惊、mask好港、superlayer是當(dāng)前l(fā)ayer的這些屬性的presentationLayer
*/
- (nullable instancetype)presentationLayer;
/* modelLayer
* 對presentationLayer調(diào)用,返回當(dāng)前模型值米罚。
* 對非presentationLayer調(diào)用钧汹,返回本身。
* 在生成表示層的事務(wù)完成后調(diào)用此方法的結(jié)果未定義阔拳。
*/
- (instancetype)modelLayer;
從注釋不難看出崭孤,這個presentationLayer即是我們看到的屏幕上展示的狀態(tài),而modelLayer就是我們設(shè)置完立即生效的真實(shí)狀態(tài)
總結(jié)
- 到這里糊肠,CALayer動畫的原理基本清晰了辨宠,當(dāng)有動畫加入時,presentationLayer會不斷的(從按某種插值或逼近得到的動畫路徑上)取值來進(jìn)行展示货裹,當(dāng)動畫結(jié)束被移除時則取modelLayer的狀態(tài)展示嗤形。這也是為什么我們用CABasicAnimation時,設(shè)定當(dāng)前值為fromValue時動畫執(zhí)行結(jié)束又會回到起點(diǎn)的原因弧圆,實(shí)際上動畫結(jié)束并不是回到起點(diǎn)而是到了modelLayer的位置赋兵。
雖然我們可以使用fillMode控制它結(jié)束時保持狀態(tài),但這種方法在動畫執(zhí)行完之后并沒有將動畫從渲染樹中移除(因?yàn)槲覀冃枰O(shè)置animation.removedOnCompletion = NO才能讓fillMode生效)搔预。如果我們想讓動畫停在終點(diǎn)霹期,更合理的辦法是一開始就將layer設(shè)置成終點(diǎn)狀態(tài),其實(shí)前文提到的UIView的block動畫就是這么做的拯田。 - UIView持有一個CALayer負(fù)責(zé)展示历造,view是這個layer的delegate。改變view的屬性實(shí)際上是在改變它持有的layer的屬性,layer屬性發(fā)生改變時會調(diào)用代理方法actionForLayer: forKey: 來得知此次變化是否需要動畫吭产。對同一個屬性疊加動畫會從當(dāng)前展示狀態(tài)開始疊加并最終停在modelLayer的真實(shí)位置侣监。
- CALayer內(nèi)部控制兩個屬性presentationLayer和modelLayer,modelLayer為當(dāng)前l(fā)ayer真實(shí)的狀態(tài)臣淤,presentationLayer為當(dāng)前l(fā)ayer在屏幕上展示的狀態(tài)橄霉。presentationLayer會在每次屏幕刷新時更新狀態(tài),如果有動畫則根據(jù)動畫獲取當(dāng)前狀態(tài)進(jìn)行繪制邑蒋,動畫移除后則取modelLayer的狀態(tài)姓蜂。
CALayer和UIView
CALayer和UIView的區(qū)別
1.UIView是UIKit的(只能iOS使用),CALayer是QuartzCore的(iOS和mac os通用)
2.UIView繼承UIResponder,CALayer繼承NSObject,UIView比CALayer多了一個事件處理的功能医吊,也就是說覆糟,CALayer不能處理用戶的觸摸事件,而UIView可以
3.UIView來自CALayer遮咖,是CALayer的高層實(shí)現(xiàn)和封裝滩字,UIView的所有特性來源于CALayer支持
4.CABasicAnimation,CAAnimation御吞,CAKeyframeAnimation等動畫類都需要加到CALayer上
其實(shí)UIView之所以能顯示在屏幕上麦箍,完全是因?yàn)樗鼉?nèi)部的一個圖層,在創(chuàng)建UIView對象時陶珠,UIView內(nèi)部會自動創(chuàng)建一個圖層(即CALayer對象)挟裂,通過UIView的layer屬性可以訪問這個層。
@property(nonatomic,readonly,retain) CALayer *layer;
當(dāng)UIView需要顯示到屏幕上時揍诽,會調(diào)用drawRect:方法進(jìn)行繪圖诀蓉,并且會將所有內(nèi)容繪制在自己的圖層上,繪圖完畢后暑脆,系統(tǒng)會將圖層拷貝到屏幕上渠啤,于是就完成了UIView的顯示
換句話說,UIView本身不具備顯示的功能添吗,是它內(nèi)部的層才有顯示功能
CoreAnimation
CAAnimation:核心動畫的基礎(chǔ)類沥曹,不能直接使用,負(fù)責(zé)動畫運(yùn)行時間碟联、速度的控制妓美,本身實(shí)現(xiàn)了CAMediaTiming協(xié)議。
CAPropertyAnimation:屬性動畫的基類(通過屬性進(jìn)行動畫設(shè)置鲤孵,注意是可動畫屬性)壶栋,不能直接使用。
CAAnimationGroup:動畫組普监,動畫組是一種組合模式設(shè)計贵试,可以通過組合動畫組來進(jìn)行所有動畫行為的統(tǒng)一控制丧没,組中的所有動畫效果可以并發(fā)執(zhí)行。
CATransition:轉(zhuǎn)場動畫锡移,通過濾鏡進(jìn)行動畫效果設(shè)置。
CABasicAnimation:基礎(chǔ)動畫漆际,通過屬性修改進(jìn)行動畫參數(shù)控制淆珊,只有初始狀態(tài)和結(jié)束狀態(tài)。
CAKeyframeAnimation:關(guān)鍵幀動畫奸汇,同樣是通過屬性進(jìn)行動畫參數(shù)控制施符,但是同基礎(chǔ)動畫不同的是它可以有多個狀態(tài)控制。