書寫的很好板丽,翻譯的也棒扔亥!感謝譯者挂签,感謝感謝!
iOS-Core-Animation-Advanced-Techniques
CALayer
和UIView最大的不同是CALayer不處理用戶的交互凯力。響應(yīng)鏈(iOS通過(guò)視圖層級(jí)關(guān)系用來(lái)傳送觸摸事件的機(jī)制)
但是為什么iOS要基于UIView和CALayer提供兩個(gè)平行的層級(jí)關(guān)系呢茵瘾?為什么不用一個(gè)簡(jiǎn)單的層級(jí)來(lái)處理所有事情呢?原因在于要做職責(zé)分離沮协,這樣也能避免很多重復(fù)代碼龄捡。
CALayer的功能
陰影卓嫂,圓角慷暂,帶顏色的邊框
3D變換
非矩形范圍
透明遮罩
多級(jí)非線性動(dòng)畫
更需要使用CALayer
開發(fā)同時(shí)可以在Mac OS上運(yùn)行的跨平臺(tái)應(yīng)用
使用多種CALayer的子類,并且不想創(chuàng)建額外的UIView去包封裝它們所有晨雳。
做一些對(duì)性能特別挑剔的工作行瑞,比如對(duì)UIView一些可忽略不計(jì)的操作都會(huì)引起顯著的不同(盡管如此,你可能會(huì)直接想使用OpenGL繪圖)
<1> contents屬性
self.layerView.layer.contents= (__bridge ?id)image.CGImage;
<2> contentGravity ?≈ ?contentMode
self.layerView.layer.contentsGravity =kCAGravityResizeAspect;
<3> contentsScale? --? (用來(lái)決定圖層內(nèi)容應(yīng)該以怎樣的分辨率來(lái)渲染不關(guān)心屏幕的拉伸因素而總是默認(rèn)為1.0)餐禁。
contentsScale的目的并不是那么明顯血久。因?yàn)閏ontents由于設(shè)置了contentsGravity屬性,所以它已經(jīng)被拉伸以適應(yīng)圖層的邊界帮非。
contentsScale屬性其實(shí)屬于支持高分辨率(又稱Hi-DPI或Retina)屏幕機(jī)制的一部分氧吐。它用來(lái)判斷在繪制圖層的時(shí)候應(yīng)該為寄宿圖創(chuàng)建的空間大小,和需要顯示的圖片的拉伸度(假設(shè)并沒(méi)有設(shè)置contentsGravity屬性)末盔。
當(dāng)用代碼的方式來(lái)處理寄宿圖的時(shí)候筑舅,一定要記住要手動(dòng)的設(shè)置圖層的contentsScale屬性,否則陨舱,你的圖片在Retina設(shè)備上就顯示得不正確啦翠拣。代碼如下:
layer.contentsScale = [UIScreenmainScreen].scale;? // 以Retina的質(zhì)量來(lái)顯示文字。
<4> maskToBounds? ≈? clipsToBounds
UIView有一個(gè)叫做clipsToBounds的屬性可以用來(lái)決定是否顯示超出邊界的內(nèi)容游盲,CALayer對(duì)應(yīng)的屬性叫做masksToBounds误墓。
<5> contentsRect? --? 圖片拼合
點(diǎn) —— 在iOS和Mac OS中最常見(jiàn)的坐標(biāo)體系蛮粮。點(diǎn)就像是虛擬的像素,也被稱作邏輯像素谜慌。在標(biāo)準(zhǔn)設(shè)備上然想,一個(gè)點(diǎn)就是一個(gè)像素,但是在Retina設(shè)備上欣范,一個(gè)點(diǎn)等于2*2個(gè)像素又沾。iOS用點(diǎn)作為屏幕的坐標(biāo)測(cè)算體系就是為了在Retina設(shè)備和普通設(shè)備上能有一致的視覺(jué)效果。
像素 —— 物理像素坐標(biāo)并不會(huì)用來(lái)屏幕布局熙卡,但是仍然與圖片有相對(duì)關(guān)系杖刷。UIImage是一個(gè)屏幕分辨率解決方案,所以指定點(diǎn)來(lái)度量大小驳癌。但是一些底層的圖片表示如CGImage就會(huì)使用像素滑燃,所以你要清楚在Retina設(shè)備和普通設(shè)備上,它們表現(xiàn)出來(lái)了不同的大小颓鲜。
單位 —— 對(duì)于與圖片大小或是圖層邊界相關(guān)的顯示表窘,單位坐標(biāo)是一個(gè)方便的度量方式, 當(dāng)大小改變的時(shí)候甜滨,也不需要再次調(diào)整乐严。單位坐標(biāo)在OpenGL這種紋理坐標(biāo)系統(tǒng)中用得很多,Core Animation中也用到了單位坐標(biāo)衣摩。
用于 圖片拼合 :
layer.contents= (__bridgeid)image.CGImage;
layer.contentsGravity=kCAGravityResizeAspect;
layer.contentsRect= rect;
<6> contentsCenter? --? 默認(rèn){0, 0, 1, 1}? --? 可拉伸視圖
contentsCenter其實(shí)是一個(gè)CGRect昂验,它定義了一個(gè)固定的邊框和一個(gè)在圖層上可拉伸的區(qū)域。
layer.contents = (__bridgeid)image.CGImage;
layer.contentsCenter = rect;
(1)-drawRect:
(2)CALayer ?--? CALayerDelegate
- (void)displayLayer:(CALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; ?-- ? CALayer創(chuàng)建了一個(gè)合適尺寸的空寄宿圖(尺寸由bounds和contentsScale決定)和一個(gè)Core Graphics的繪制上下文環(huán)境
[layer ?display]; ?-- ?強(qiáng)制重畫
UIView? --? frame艾扮,bounds既琴,center? =>? CALayer? --? frame,bounds泡嘴,position
center和position都代表了相對(duì)于父圖層anchorPoint所在的位置甫恩。
視圖的frame,bounds和center屬性僅僅是存取方法酌予,當(dāng)操縱視圖的frame磺箕,實(shí)際上是在改變位于視圖下方CALayer的frame,不能夠獨(dú)立于圖層之外改變視圖的frame抛虫。
frame?是根據(jù)bounds松靡,position和transform計(jì)算而來(lái)缸夹。
anchorPoint ?-- ?錨點(diǎn)
圖層的 anchorPoint 通過(guò)?position?來(lái)控制它的 frame 的位置变隔,你可以認(rèn)為anchorPoint是用來(lái)移動(dòng)圖層的把柄。
坐標(biāo)系 ?-- ?檢測(cè)點(diǎn)擊圖層間會(huì)相互轉(zhuǎn)換坐標(biāo)
CALayer 給不同坐標(biāo)系之間的圖層轉(zhuǎn)換
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
翻轉(zhuǎn)坐標(biāo)系
geometryFlipped = YES
Z坐標(biāo)軸
和UIView嚴(yán)格的二維坐標(biāo)系不同轿偎,CALayer存在于一個(gè)三維空間當(dāng)中。
CALayer還有另外兩個(gè)屬性阅茶,zPosition 和 anchorPointZ
最實(shí)用的功能就是改變圖層的顯示順序
self.greenView.layer.zPosition=1.0f;
- containsPoint:
-hitTest:
zPosition屬性可以明顯改變屏幕上圖層的順序蛛枚,但不能改變事件傳遞的順序。
自動(dòng)布局? --? Core Animation對(duì)自動(dòng)調(diào)整和自動(dòng)布局支持的缺乏脸哀。
CALayerDelegate如下函數(shù):
- (void) layoutSublayersOfLayer:(CALayer *) layer;
"不能像`UIView`的`autoresizingMask`和`constraints`屬性做到自適應(yīng)屏幕旋轉(zhuǎn)蹦浦。"這也是為什么最好使用視圖而不是單獨(dú)的圖層來(lái)構(gòu)建應(yīng)用程序的另一個(gè)重要原因之一。
圓角? --? conrnerRadius
默認(rèn)情況下撞蜂,這個(gè)曲率值只影響背景顏色而不影響背景圖片或是子圖層盲镶。不過(guò),如果把masksToBounds = YES的話蝌诡,圖層里面的所有東西都會(huì)被截取溉贿。
圖層邊框? --? borderWidth,borderColor
陰影? --? shadowOpacity浦旱,shadowOffset宇色,shadowRadius,shadowColor
shadowRadius屬性控制著陰影的模糊度颁湖,當(dāng)它的值是0的時(shí)候宣蠕,陰影就和視圖一樣有一個(gè)非常確定的邊界線。當(dāng)值越來(lái)越大的時(shí)候甥捺,邊界線看上去就會(huì)越來(lái)越模糊和自然抢蚀。
Tip:陰影裁剪
和圖層邊框不同,圖層的陰影繼承自內(nèi)容的外形镰禾,而不是根據(jù)邊界和角半徑來(lái)確定皿曲。
陰影和裁剪限制:陰影通常就是在Layer的邊界之外,如果你開啟了masksToBounds屬性羡微,所有從圖層中突出來(lái)的內(nèi)容都會(huì)被才剪掉谷饿。maskToBounds屬性裁剪掉了陰影和內(nèi)容
你需要用到兩個(gè)圖層:一個(gè)只畫陰影的空的外圖層惶我,和一個(gè)用masksToBounds裁剪內(nèi)容的內(nèi)圖層妈倔。
shadowPath
如果是一個(gè)矩形或者是圓,用CGPath會(huì)相當(dāng)簡(jiǎn)單明了绸贡。但是如果是更加復(fù)雜一點(diǎn)的圖形盯蝴,UIBezierPath類會(huì)更合適,它是一個(gè)由UIKit提供的在CGPath基礎(chǔ)上的Objective-C包裝類听怕。
圖層蒙板
CALayer有一個(gè)屬性叫做 mask 可以解決這個(gè)問(wèn)題捧挺。這個(gè)屬性本身就是個(gè)CALayer類型,有和其他圖層一樣的繪制和布局屬性尿瞭。它類似于一個(gè)子圖層闽烙,相對(duì)于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個(gè)普通的子圖層。不同于那些繪制在父圖層中的子圖層黑竞,mask圖層定義了父圖層的部分可見(jiàn)區(qū)域捕发。
mask圖層 真正重要的是圖層的輪廓。mask屬性就像是一個(gè)餅干切割機(jī)很魂,mask圖層實(shí)心的部分會(huì)被保留下來(lái)扎酷,其他的則會(huì)被拋棄。
CALayer蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態(tài)圖遏匆。任何有圖層構(gòu)成的都可以作為mask屬性法挨,這意味著你的蒙板可以通過(guò)代碼甚至是動(dòng)畫實(shí)時(shí)生成。
拉伸過(guò)濾
minificationFilter 和 magnificationFilter
當(dāng)圖片需要顯示不同的大小的時(shí)候幅聘,有一種叫做拉伸過(guò)濾的算法就起到作用了凡纳。它作用于原圖的像素上并根據(jù)需要生成新的像素顯示在屏幕上。
CALayer為此提供了三種拉伸過(guò)濾方法帝蒿,
kCAFilterLinear? --? 默認(rèn)惫企,采用雙線性濾波算法,它在大多數(shù)情況下都表現(xiàn)良好陵叽。雙線性濾波算法通過(guò)對(duì)多個(gè)像素取樣最終生成新的值狞尔,得到一個(gè)平滑的表現(xiàn)不錯(cuò)的拉伸。但是當(dāng)放大倍數(shù)比較大的時(shí)候圖片就模糊不清了巩掺。
kCAFilterNearest? -- 三線性濾波算法存儲(chǔ)了多個(gè)大小情況下的圖片(也叫多重貼圖)偏序,并三維取樣,同時(shí)結(jié)合大圖和小圖的存儲(chǔ)進(jìn)而得到最后的結(jié)果胖替。
kCAFilterTrilinear 最近過(guò)濾? -- 就是取樣最近的單像素點(diǎn)而不管其他的顏色研儒。這樣做非常快独令,也不會(huì)使圖片模糊端朵。但是,最明顯的效果就是燃箭,會(huì)使得壓縮圖片更糟冲呢,圖片放大之后也顯得塊狀或是馬賽克嚴(yán)重。
線性過(guò)濾保留了形狀招狸,最近過(guò)濾則保留了像素的差異敬拓。
例子!H瓜贰乘凸!
組透明? --? UIViewGroupOpacity? &&? shouldRasterize(rasterizationScale)
UIView有一個(gè)叫做alpha的屬性來(lái)確定視圖的透明度。CALayer -- opacity累榜,這兩個(gè)屬性都是影響子層級(jí)的营勤。一個(gè)問(wèn)題,透明度的混合疊加。
當(dāng)你顯示一個(gè)50%透明度的圖層時(shí)葛作,圖層的每個(gè)像素都會(huì)一般顯示自己的顏色醒第,另一半顯示圖層下面的顏色。這是正常的透明度的表現(xiàn)进鸠。但是如果圖層包含一個(gè)同樣顯示50%透明的子圖層時(shí)稠曼,你所看到的視圖,50%來(lái)自子視圖客年,25%來(lái)了圖層本身的顏色霞幅,另外的25%則來(lái)自背景色。
(1)通過(guò)設(shè)置Info.plist文件中的UIViewGroupOpacity = YES來(lái)達(dá)到這個(gè)效果量瓜,但是這個(gè)設(shè)置會(huì)影響到這個(gè)應(yīng)用司恳,整個(gè)app可能會(huì)受到不良影響。
(2)另一個(gè)方法就是绍傲,你可以設(shè)置CALayer的一個(gè)叫做 shouldRasterize = YES 屬性來(lái)實(shí)現(xiàn)組透明的效果扔傅,在應(yīng)用透明度之前,圖層及其子圖層都會(huì)被整合成一個(gè)整體的圖片烫饼,這樣就沒(méi)有透明度混合的問(wèn)題了猎塞。
shouldRasterize -> rasterizationScale ?要確保你設(shè)置了rasterizationScale屬性去匹配屏幕,以防止出現(xiàn)Retina屏幕像素化的問(wèn)題杠纵。
CGAffineTransform? &&? CATransform3D
仿射變換
仿射:平行的兩條線在變換之后任然保持平行
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
UIView (transform) == CALayer (affineTransform)
混合變換
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
CGAffineTransformIdentity ?-- ?單位矩陣
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2) -- 混合兩個(gè)變化矩陣
剪切變換
很少需要直接設(shè)置CGAffineTransform的值荠耽。除非需要?jiǎng)?chuàng)建一個(gè)斜切的變換,Core Graphics并沒(méi)有提供直接的函數(shù)比藻。傾斜
3D變換
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
透視投影
投影變換(又稱作z變換)
CATransform3D的m34元素铝量,用來(lái)做透視
滅點(diǎn)
Core Animation定義了這個(gè)點(diǎn)位于變換圖層的 anchorPoint
當(dāng)改變一個(gè)圖層的position,你也改變了它的滅點(diǎn)银亲,做3D變換的時(shí)候要時(shí)刻記住這一點(diǎn)慢叨,當(dāng)你視圖通過(guò)調(diào)整m34來(lái)讓它更加有3D效果,應(yīng)該首先把它放置于屏幕中央务蝠,然后通過(guò)平移來(lái)把它移動(dòng)到指定位置(而不是直接改變它的position)拍谐,這樣所有的3D圖層都共享一個(gè)滅點(diǎn)。
sublayerTransform屬性? --? CATransform3D類型
self.containerView.layer.sublayerTransform= perspective;
背面 ?-- ?旋轉(zhuǎn)M_PI
圖層是雙面繪制的请梢,反面顯示的是正面的一個(gè)鏡像圖片赠尾。
CALayer有一個(gè)叫做doubleSided的屬性來(lái)控制圖層的背面是否要被繪制。這是一個(gè)BOOL類型毅弧,默認(rèn)為YES,如果設(shè)置為NO当窗,那么當(dāng)圖層正面從相機(jī)視角消失的時(shí)候够坐,它將不會(huì)被繪制。提升性能。
扁平化圖層 ?-- ?Y元咙,Z軸抵消操作
-在相同場(chǎng)景下的任何3D表面必須和同樣的圖層保持一致梯影,這是因?yàn)槊總€(gè)的父視圖都把它的子視圖扁平化了。
固體對(duì)象 ?-- ?cube
光亮和陰影
可以通過(guò)改變每個(gè)面的背景顏色或者直接用帶光亮效果的圖片來(lái)調(diào)整庶香。
如果需要動(dòng)態(tài)地創(chuàng)建光線效果甲棍,你可以根據(jù)每個(gè)視圖的方向應(yīng)用不同的alpha值做出半透明的陰影圖層,但為了計(jì)算陰影圖層的不透明度赶掖,你需要得到每個(gè)面的正太向量(垂直于表面的向量)感猛,然后根據(jù)一個(gè)想象的光源計(jì)算出兩個(gè)向量叉乘結(jié)果。叉乘代表了光源和圖層之間的角度奢赂,從而決定了它有多大程度上的光亮陪白。
我們用GLKit框架來(lái)做向量的計(jì)算(你需要引入GLKit庫(kù)來(lái)運(yùn)行代碼),每個(gè)面的CATransform3D都被轉(zhuǎn)換成GLKMatrix4膳灶,然后通過(guò)GLKMatrix4GetMatrix3函數(shù)得出一個(gè)3×3的旋轉(zhuǎn)矩陣咱士。這個(gè)旋轉(zhuǎn)矩陣指定了圖層的方向,然后可以用它來(lái)得到正太向量的值轧钓。
點(diǎn)擊事件
問(wèn)題在于視圖順序序厉。在第三章中我們簡(jiǎn)要提到過(guò),點(diǎn)擊事件的處理由視圖在父視圖中的順序決定的毕箍,并不是3D空間中的Z軸順序脂矫。
因?yàn)楸硨?duì)相機(jī)而隱藏的視圖仍然會(huì)響應(yīng)點(diǎn)擊事件(這和通過(guò)設(shè)置hidden屬性或者設(shè)置alpha為0而隱藏的視圖不同,那兩種方式將不會(huì)響應(yīng)事件)
隱式動(dòng)畫 ?-- ?平滑過(guò)渡
當(dāng)你改變CALayer的一個(gè)可做動(dòng)畫的屬性霉晕,它并不能立刻在屏幕上體現(xiàn)出來(lái)庭再。相反,它是從先前的值平滑過(guò)渡到新的值牺堰。這一切都是默認(rèn)的行為拄轻,你不需要做額外的操作。
這其實(shí)就是所謂的隱式動(dòng)畫伟葫。之所以叫隱式是因?yàn)槲覀儾?b>沒(méi)有指定任何動(dòng)畫的類型恨搓。我們僅僅改變了一個(gè)屬性,然后Core Animation來(lái)決定如何并且何時(shí)去做動(dòng)畫筏养。
改變一個(gè)屬性? -> ?動(dòng)畫執(zhí)行時(shí)間 == 當(dāng)前事務(wù)的設(shè)置斧抱,動(dòng)畫類型 ==?圖層行為。
事務(wù) CATransaction
?實(shí)際上是Core Animation用來(lái)包含一系列屬性動(dòng)畫集合的機(jī)制渐溶,任何用指定事務(wù)去改變可以做動(dòng)畫的圖層屬性都不會(huì)立刻發(fā)生變化辉浦,而是當(dāng)事務(wù)一旦提交的時(shí)候開始用一個(gè)動(dòng)畫過(guò)渡到新值。
但是可以用+begin和+commit分別來(lái)入椌シ或者出棧宪郊〉嗨。可以通過(guò)+setAnimationDuration:方法設(shè)置當(dāng)前事務(wù)的動(dòng)畫時(shí)間,或者通過(guò)+animationDuration方法來(lái)獲取值(默認(rèn)0.25秒)
Core Animation在每個(gè)run loop周期中自動(dòng)開始一次新的事務(wù)(run loop是iOS負(fù)責(zé)收集用戶輸入弛槐,處理定時(shí)器或者網(wǎng)絡(luò)事件并且重新繪制屏幕的東西)懊亡,即使你不顯式的用[CATransaction begin]開始一次事務(wù),任何在一次run loop循環(huán)中屬性的改變都會(huì)被集中起來(lái)乎串,然后做一次0.25秒的動(dòng)畫店枣。
UIView有兩個(gè)方法,+beginAnimations:context:和+commitAnimations叹誉,和CATransaction的+begin和+commit方法類似鸯两。animateWithDuration:animations
CATranscation ?-- ?setCompletionBlock
隱式動(dòng)畫是如何被UIKit禁用掉呢?
Core Animation通常對(duì)CALayer的所有屬性(可動(dòng)畫的屬性)做動(dòng)畫桂对,但是UIView把它關(guān)聯(lián)的圖層的這個(gè)特性關(guān)閉了甩卓。
隱式動(dòng)畫實(shí)現(xiàn)過(guò)程:? --? 對(duì)應(yīng)官方文檔?動(dòng)作對(duì)象
我們把改變屬性時(shí)CALayer自動(dòng)應(yīng)用的動(dòng)畫稱作行為,當(dāng)CALayer的屬性被修改時(shí)候蕉斜,它會(huì)調(diào)用-actionForKey:方法逾柿,傳遞屬性的名稱。剩下的操作都在CALayer的頭文件中有詳細(xì)的說(shuō)明宅此,實(shí)質(zhì)上是如下幾步:
(1)圖層首先檢測(cè)它是否有委托机错,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法。如果有父腕,直接調(diào)用并返回結(jié)果弱匪。
(2)如果沒(méi)有委托,或者委托沒(méi)有實(shí)現(xiàn)-actionForLayer:forKey方法璧亮,圖層接著檢查包含屬性名稱對(duì)應(yīng)行為映射的actions字典萧诫。
(3)如果actions字典沒(méi)有包含對(duì)應(yīng)的屬性,那么圖層接著在它的style字典接著搜索屬性名枝嘶。
(4)最后帘饶,如果在style里面也找不到對(duì)應(yīng)的行為,那么圖層將會(huì)直接調(diào)用定義了每個(gè)屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法群扶。
所以一輪完整的搜索結(jié)束之后及刻,-actionForKey:要么返回空(這種情況下將不會(huì)有動(dòng)畫發(fā)生),要么是CAAction協(xié)議對(duì)應(yīng)的對(duì)象竞阐,最后CALayer拿這個(gè)結(jié)果去對(duì)先前和當(dāng)前的值做動(dòng)畫缴饭。
解釋了UIKit是如何禁用隱式動(dòng)畫的:每個(gè)UIView對(duì)它關(guān)聯(lián)的圖層都扮演了一個(gè)委托,并且提供了-actionForLayer:forKey的實(shí)現(xiàn)方法骆莹。當(dāng)不在一個(gè)動(dòng)畫塊的實(shí)現(xiàn)中颗搂,UIView對(duì)所有圖層行為返回nil,但是在動(dòng)畫block范圍之內(nèi)汪疮,它就返回了一個(gè)非空值峭火。
返回nil并不是禁用隱式動(dòng)畫唯一的辦法毁习,[CATransaction setDisableActions:YES];智嚷,可以用來(lái)對(duì)所有屬性打開或者關(guān)閉隱式動(dòng)畫卖丸。
總結(jié):
(1)UIView關(guān)聯(lián)的圖層禁用了隱式動(dòng)畫,對(duì)這種圖層做動(dòng)畫的唯一辦法就是使用UIView的動(dòng)畫函數(shù)(而不是依賴CATransaction)盏道,或者繼承UIView稍浆,并覆蓋-actionForLayer:forKey:方法,或者直接創(chuàng)建一個(gè)顯式動(dòng)畫猜嘱。
(2)對(duì)于單獨(dú)存在的圖層衅枫,我們可以通過(guò)實(shí)現(xiàn)圖層的-actionForLayer:forKey:委托方法,或者提供一個(gè)actions字典來(lái)控制隱式動(dòng)畫朗伶。
如何創(chuàng)建一個(gè)合適的行為對(duì)象呢弦撩?? --? 自定義的actions字典 && 使用委托
推進(jìn)過(guò)渡 ?CATransition?
呈現(xiàn)與模型 - 呈現(xiàn)==定時(shí)器動(dòng)畫&點(diǎn)擊事件
當(dāng)你改變一個(gè)圖層的屬性,屬性值的確是立刻更新的论皆,但是屏幕上并沒(méi)有馬上發(fā)生改變益楼。這是因?yàn)槟阍O(shè)置的屬性并沒(méi)有直接調(diào)整圖層的外觀,相反点晴,他只是定義了圖層動(dòng)畫結(jié)束之后將要變化的外觀感凤。
Core Animation扮演了一個(gè)控制器的角色,并且負(fù)責(zé)根據(jù)圖層行為和事務(wù)設(shè)置去不斷更新視圖的這些屬性在屏幕上的狀態(tài)粒督。
我們討論的就是一個(gè)典型的微型MVC模式陪竿。CALayer是一個(gè)連接用戶界面(就是MVC中的view)虛構(gòu)的類,但是在界面本身這個(gè)場(chǎng)景下屠橄,CALayer的行為更像是存儲(chǔ)了視圖如何顯示和動(dòng)畫的數(shù)據(jù)模型族跛。實(shí)際上,在蘋果自己的文檔中锐墙,圖層樹通常都是值的圖層樹模型礁哄。
每個(gè)圖層屬性的顯示值都被存儲(chǔ)在一個(gè)叫做呈現(xiàn)圖層的獨(dú)立圖層當(dāng)中,他可以通過(guò)-presentationLayer方法來(lái)訪問(wèn)贮匕。
圖層樹 -- 呈現(xiàn)樹姐仅。圖層樹中所有圖層的呈現(xiàn)圖層?= 呈現(xiàn)樹
NOTE:呈現(xiàn)圖層僅僅當(dāng)圖層首次被提交(就是首次第一次在屏幕上顯示)的時(shí)候創(chuàng)建,所以在那之前調(diào)用-presentationLayer將會(huì)返回nil刻盐。
有一個(gè)叫做–modelLayer的方法掏膏。在呈現(xiàn)圖層上調(diào)用–modelLayer將會(huì)返回它正在呈現(xiàn)所依賴的CALayer。通常在一個(gè)圖層上調(diào)用-modelLayer會(huì)返回–self
兩種情況下呈現(xiàn)圖層會(huì)變得很有用敦锌,一個(gè)是同步動(dòng)畫馒疹,一個(gè)是處理用戶交互。
(1)如果你在實(shí)現(xiàn)一個(gè)基于定時(shí)器的動(dòng)畫乙墙,而不僅僅是基于事務(wù)的動(dòng)畫颖变,這個(gè)時(shí)候準(zhǔn)確地知道在某一時(shí)刻圖層顯示在什么位置就會(huì)對(duì)正確擺放圖層很有用了生均。
(2)如果你想讓你做動(dòng)畫的圖層響應(yīng)用戶輸入,你可以使用-hitTest:方法來(lái)判斷指定圖層是否被觸摸腥刹,這時(shí)候?qū)?b>呈現(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會(huì)顯得更有意義马胧,因?yàn)槌尸F(xiàn)圖層代表了用戶當(dāng)前看到的圖層位置,而不是當(dāng)前動(dòng)畫結(jié)束之后的位置衔峰。
[self.colorLayer.presentationLayer ?hitTest:point]
如果修改代碼讓-hitTest:直接作用于colorLayer而不是呈現(xiàn)圖層佩脊,點(diǎn)擊目標(biāo)區(qū)域方塊會(huì)變色,因?yàn)閏olorLayer是當(dāng)前動(dòng)畫結(jié)束之后的值垫卤。而呈現(xiàn)圖層是實(shí)時(shí)的值威彰。
顯式動(dòng)畫
屬性動(dòng)畫? --? 基礎(chǔ)和關(guān)鍵幀
基礎(chǔ)動(dòng)畫:
CABasicAnimation <- ?CAPropertyAnimation <- CAAnimation (CA所有動(dòng)畫類型的抽象基類)
(1)CAAnimation提供了一個(gè)計(jì)時(shí)函數(shù),一個(gè)委托(用于反饋動(dòng)畫狀態(tài))以及一個(gè)removedOnCompletion穴肘,用于標(biāo)識(shí)動(dòng)畫是否該在結(jié)束后自動(dòng)釋放(默認(rèn)YES歇盼,為了防止內(nèi)存泄露)。
CAAction? &&? CAMediaTiming
(2)CABasicAnimation 添加了 id fromValue (動(dòng)畫開始之前屬性的值)评抚,id toValue (動(dòng)畫結(jié)束之后的值)豹缀,id byValue (動(dòng)畫執(zhí)行過(guò)程中改變的值)。
當(dāng)之前在使用隱式動(dòng)畫的時(shí)候盈咳,實(shí)際上它就是用例子中CABasicAnimation來(lái)實(shí)現(xiàn)的(回憶第七章耿眉,我們?cè)?actionForLayer:forKey:委托方法打印出來(lái)的結(jié)果就是CABasicAnimation)。
把動(dòng)畫設(shè)置成一個(gè)圖層的行為(然后通過(guò)改變屬性值來(lái)啟動(dòng)動(dòng)畫)是到目前為止同步屬性值和動(dòng)畫狀態(tài)最簡(jiǎn)單的方式了鱼响。
animation.fromValue = (__bridge id)self.colorLayer.backgroundColor;
self.colorLayer.backgroundColor = color.CGColor;
兩個(gè)問(wèn)題:
(1)已經(jīng)正在進(jìn)行一段動(dòng)畫鸣剪,需要從呈現(xiàn)圖層那里去獲得fromValue,而不是模型圖層丈积。
(2)這里的圖層并不是UIView關(guān)聯(lián)的圖層筐骇,我們需要用CATransaction來(lái)禁用隱式動(dòng)畫行為,否則默認(rèn)的圖層行為會(huì)干擾我們的顯式動(dòng)畫
CAAnimationDelegate
為了知道一個(gè)顯式動(dòng)畫在何時(shí)結(jié)束江滨,我們需要使用一個(gè)實(shí)現(xiàn)了CAAnimationDelegate協(xié)議的delegate
我們用-animationDidStop:finished:方法在動(dòng)畫結(jié)束之后來(lái)更新圖層的backgroundColor铛纬。
Note:
對(duì)CAAnimation而言,使用委托模式而不是一個(gè)完成塊會(huì)帶來(lái)一個(gè)問(wèn)題唬滑,就是當(dāng)你有多個(gè)動(dòng)畫的時(shí)候告唆,無(wú)法在在回調(diào)方法中區(qū)分。
resolution:
當(dāng)使用-addAnimation:forKey:把動(dòng)畫添加到圖層晶密,這里有一個(gè)到目前為止我們都設(shè)置為nil的key參數(shù)擒悬。<1>這里的鍵是-animationForKey:方法找到對(duì)應(yīng)動(dòng)畫的唯一標(biāo)識(shí)符,<2>而當(dāng)前動(dòng)畫的所有鍵都可以用animationKeys獲取稻艰。<3>如果我們對(duì)每個(gè)動(dòng)畫都關(guān)聯(lián)一個(gè)唯一的鍵懂牧,就可以對(duì)每個(gè)圖層循環(huán)所有鍵,然后調(diào)用-animationForKey:來(lái)比對(duì)結(jié)果尊勿。盡管這不是一個(gè)優(yōu)雅的實(shí)現(xiàn)僧凤。
更加簡(jiǎn)單的方法:
CAAnimation實(shí)現(xiàn)了KVC(鍵-值-編碼)協(xié)議畜侦,于是你可以用-setValue:forKey:和-valueForKey:方法來(lái)存取屬性。CAAnimation更像一個(gè)NSDictionary躯保,可以讓你隨意設(shè)置鍵值對(duì)旋膳,即使和你使用的動(dòng)畫類所聲明的屬性并不匹配。
可以對(duì)動(dòng)畫用任意類型打標(biāo)簽
關(guān)鍵幀動(dòng)畫? --? CAKeyframeAnimation
不限制于設(shè)置一個(gè)起始和結(jié)束的值吻氧,而是可以根據(jù)一連串隨意的值來(lái)做動(dòng)畫溺忧。
(1)animation.values= @[...]
開始和結(jié)束的顏色都是藍(lán)色咏连,CAKeyframeAnimation不能自動(dòng)把當(dāng)前值作為第一幀盯孙,開始和結(jié)束設(shè)置其他顏色都會(huì)出現(xiàn)跳幀。當(dāng)然可以創(chuàng)建一個(gè)結(jié)束和開始值不同的動(dòng)畫祟滴,需要在動(dòng)畫啟動(dòng)之前手動(dòng)更新屬性和最后一幀的值保持一致振惰。
動(dòng)畫漸變效果有些奇怪,需要調(diào)整一下緩沖 --> 第十章
(2)CAKeyframeAnimation? --> ?CGPath
三次貝塞爾曲線垄懂,它是一種使用開始點(diǎn)骑晶,結(jié)束點(diǎn)和另外兩個(gè)控制點(diǎn)來(lái)定義形狀的曲線 ? ? ? ? ? ? ? ? ? ? (CG ?&& ?UIBezierPath) -- ?path -> 繪制 + 關(guān)聯(lián)動(dòng)畫
創(chuàng)建path -> 用圖層繪制path -> 創(chuàng)建運(yùn)動(dòng)層 -> 創(chuàng)建動(dòng)畫關(guān)聯(lián)path,添加到運(yùn)動(dòng)層
CAKeyFrameAnimation -- rotationMode = kCAAnimationRotateAuto
虛擬屬性
transform.rotation關(guān)鍵路徑應(yīng)用動(dòng)畫草慧,而不是transform本身
(1)我們可以不通過(guò)關(guān)鍵幀一步旋轉(zhuǎn)多于180度的動(dòng)畫桶蛔。
(2)可以用相對(duì)值而不是絕對(duì)值旋轉(zhuǎn)(設(shè)置byValue而不是toValue)。
(3)可以不用創(chuàng)建CATransform3D漫谷,而是使用一個(gè)簡(jiǎn)單的數(shù)值來(lái)指定角度仔雷。
(4)不會(huì)和transform.position或者transform.scale沖突(同樣是使用關(guān)鍵路徑來(lái)做獨(dú)立的動(dòng)畫屬性)。
transform.rotation屬性有一個(gè)奇怪的問(wèn)題是它其實(shí)并不存在舔示。這是因?yàn)镃ATransform3D并不是一個(gè)對(duì)象碟婆,它實(shí)際上是一個(gè)結(jié)構(gòu)體,也沒(méi)有符合KVC相關(guān)屬性惕稻,transform.rotation實(shí)際上是一個(gè)CALayer用于處理動(dòng)畫變換的虛擬屬性竖共。
note: ?-- ?不能直接使用,只能做動(dòng)畫
你不可以直接設(shè)置transform.rotation或者transform.scale俺祠,他們不能被直接使用公给。當(dāng)你對(duì)他們做動(dòng)畫時(shí),Core Animation自動(dòng)地根據(jù)通過(guò)CAValueFunction來(lái)計(jì)算的值來(lái)更新transform屬性蜘渣。
CAValueFunction用于把我們賦給虛擬的transform.rotation簡(jiǎn)單浮點(diǎn)值 => 真正的用于擺放圖層的CATransform3D矩陣值淌铐。你可以通過(guò)設(shè)置CAPropertyAnimation的valueFunction屬性來(lái)改變,于是你設(shè)置的函數(shù)將會(huì)覆蓋默認(rèn)的函數(shù)宋梧。
動(dòng)畫組? --? CAAnimationGroup:animations
groupAnimation.animations= @[animation1, animation2];
過(guò)渡? --? CATransition
type = kCATransitionFade匣沼、MoveIn、Push捂龄、Reveal
subtype = kCATransitionFromRight释涛、Left加叁、Top、Bottom
隱式過(guò)渡
CATransision可以對(duì)圖層任何變化平滑過(guò)渡的事實(shí)使得它成為那些不好做動(dòng)畫的屬性圖層行為的理想候選唇撬。設(shè)置CALayer的content它匕,CATransition是默認(rèn)的行為。(視圖關(guān)聯(lián)的圖層 && 隱式動(dòng)畫的行為 特性被禁用)窖认。自己創(chuàng)建圖層可用豫柬。
對(duì)圖層樹的動(dòng)畫
CATransition并不作用于指定的圖層屬性,對(duì)隨便什么變化都支持過(guò)渡扑浸。
tricks:要確保CATransition添加到的圖層在過(guò)渡動(dòng)畫發(fā)生時(shí)不會(huì)在樹狀結(jié)構(gòu)中被移除烧给,否則CATransition將會(huì)和圖層一起被移除。一般來(lái)說(shuō)喝噪,你只需要將動(dòng)畫添加到被影響圖層的superlayer础嫡。
自定義動(dòng)畫
UIView:+transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:
options:UIViewAnimationOptionTransition_FlipFromLeft、Right酝惧、CurlUp榴鼎、CurlDown
CrossDissolve、FlipFromTop晚唇、FlipFromBottom ?-- ?翻轉(zhuǎn)酷炫動(dòng)畫
因此巫财,根據(jù)要實(shí)現(xiàn)的效果,你只用關(guān)心是用CATransition還是用UIView的過(guò)渡方法就可以了哩陕。
過(guò)渡動(dòng)畫原理:
過(guò)渡動(dòng)畫做基礎(chǔ)的原則就是對(duì)原始的圖層外觀截圖平项,然后添加一段動(dòng)畫,平滑過(guò)渡到圖層改變之后那個(gè)截圖的效果萌踱。如果我們知道如何對(duì)圖層截圖葵礼,我們就可以使用屬性動(dòng)畫來(lái)代替CATransition或者是UIKit的過(guò)渡方法來(lái)實(shí)現(xiàn)動(dòng)畫。
對(duì)圖層做截圖: -- 例子簡(jiǎn)單并鸵,但是很酷炫
CALayer有一個(gè)-renderInContext:方法鸳粉,可以通過(guò)把它繪制到Core Graphics的上下文中捕獲當(dāng)前內(nèi)容的圖片,然后在另外的視圖中顯示出來(lái)园担。
note:
-renderInContext:捕獲了圖層的圖片和子圖層届谈,但是不能對(duì)子圖層正確地處理變換效果,而且對(duì)視頻和OpenGL內(nèi)容也不起作用弯汰。但是用CATransition艰山,或者用私有的截屏方式就沒(méi)有這個(gè)限制了。
在動(dòng)畫過(guò)程中取消動(dòng)畫
- (CAAnimation *)animationForKey:(NSString *)key;
key參數(shù)來(lái)在添加動(dòng)畫之后檢索一個(gè)動(dòng)畫咏闪,不支持在動(dòng)畫運(yùn)行過(guò)程中修改動(dòng)畫芦缰,所以這個(gè)方法主要用來(lái)檢測(cè)動(dòng)畫的屬性闯睹,或者判斷它是否被添加到當(dāng)前圖層中孽文。
終止動(dòng)畫:
- (void)removeAnimationForKey:(NSString *)key;? && ?- (void)removeAllAnimations;
Tip:removedOnCompletion = NO皮官,設(shè)置不被移除缸濒,不需要的時(shí)候就要手動(dòng)移除。
CAMediaTiming協(xié)議
CAMediaTiming協(xié)議定義了在一段動(dòng)畫內(nèi)用來(lái)控制逝去時(shí)間的屬性的集合,CALayer和CAAnimation都實(shí)現(xiàn)了這個(gè)協(xié)議,所以時(shí)間可以被任意基于一個(gè)圖層或者一段動(dòng)畫的類控制诗箍。
(1)duration +?repeatCount
(2) repeatDuration = INFINITY,autoreverses
相對(duì)時(shí)間
每次討論到Core Animation挽唉,時(shí)間都是相對(duì)的滤祖,每個(gè)動(dòng)畫都有它自己描述的時(shí)間,可以獨(dú)立地加速瓶籽,延時(shí)或者偏移匠童。
beginTime -- 動(dòng)畫開始之前的的延遲時(shí)間。
speed -- ?一個(gè)時(shí)間的倍數(shù)棘劣,默認(rèn)1.0
timeOffset ?-- ?增加timeOffset只是讓動(dòng)畫從某點(diǎn)開始
timeOffset并不受speed的影響俏让,對(duì)于speed=2.0,duration=1.0茬暇,timeOffset=0.5,動(dòng)畫從最后結(jié)束的地方開始寡喝,循環(huán)一圈糙俗。
fillMode
對(duì)于beginTime非0的一段動(dòng)畫來(lái)說(shuō),會(huì)出現(xiàn)當(dāng)動(dòng)畫添加到圖層上但什么也沒(méi)發(fā)生的狀態(tài)预鬓。類似的巧骚,removeOnCompletion = NO的動(dòng)畫將會(huì)在動(dòng)畫結(jié)束的時(shí)候仍然保持之前的狀態(tài)。
保持動(dòng)畫開始之前那一幀格二,或者動(dòng)畫結(jié)束之后的那一幀劈彪。這就是所謂的填充
CAMediaTiming? -->? fillMode -->? kCAFillModeForwards、Backwards顶猜、Both沧奴、Removed
這就對(duì)避免在動(dòng)畫結(jié)束的時(shí)候急速返回提供另一種方案(removeOnCompletion = NO,另外需要給動(dòng)畫添加一個(gè)非空的鍵长窄,于是可以在不需要?jiǎng)赢嫷臅r(shí)候把它從圖層上移除滔吠。)
層級(jí)關(guān)系時(shí)間
對(duì)CALayer或者CAGroupAnimation調(diào)整duration和repeatCount/repeatDuration屬性并不會(huì)影響到子動(dòng)畫。但是beginTime挠日,timeOffset和speed屬性將會(huì)影響到子動(dòng)畫疮绷。
beginTime指定了父圖層開始動(dòng)畫(或者組合關(guān)系中的父動(dòng)畫)和對(duì)象將要開始自己動(dòng)畫之間的偏移。
全局時(shí)間
CoreAnimation有一個(gè)全局時(shí)間的概念嚣潜,也就是所謂的馬赫時(shí)間
CFTimeInterval time = CACurrentMediaTime();? -- ?設(shè)備自從上次啟動(dòng)后的秒數(shù)
真實(shí)的作用在于對(duì)動(dòng)畫的時(shí)間測(cè)量提供了一個(gè)相對(duì)值冬骚。
本地時(shí)間
每個(gè)CALayer和CAAnimation實(shí)例都有自己本地時(shí)間的概念,是根據(jù)父圖層/動(dòng)畫層級(jí)關(guān)系中的beginTime,timeOffset和speed屬性計(jì)算只冻。
轉(zhuǎn)換不同圖層之間的本地時(shí)間:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
當(dāng)用來(lái)同步不同圖層之間有不同的speed夜涕,timeOffset和beginTime的動(dòng)畫,這些方法會(huì)很有用属愤。
暫停女器,倒回和快進(jìn)
動(dòng)畫的speed ?-- ?設(shè)置動(dòng)畫的speed屬性為0可以暫停動(dòng)畫,但在動(dòng)畫被添加到圖層之后不太可能再修改它了住诸,所以不能對(duì)正在進(jìn)行的動(dòng)畫使用這個(gè)屬性驾胆。給圖層添加一個(gè)CAAnimation實(shí)際上是給動(dòng)畫對(duì)象做了一個(gè)不可改變的拷貝,所以對(duì)原始動(dòng)畫對(duì)象屬性的改變對(duì)真實(shí)的動(dòng)畫并沒(méi)有作用贱呐。相反丧诺,直接用-animationForKey:來(lái)檢索圖層正在進(jìn)行的動(dòng)畫可以返回正確的動(dòng)畫對(duì)象,但是修改它的屬性將會(huì)拋出異常奄薇。
(1)如果移除圖層正在進(jìn)行的動(dòng)畫驳阎,圖層將會(huì)急速返回動(dòng)畫之前的狀態(tài)。但如果在動(dòng)畫移除之前拷貝呈現(xiàn)圖層到模型圖層馁蒂,動(dòng)畫將會(huì)看起來(lái)暫停在那里呵晚。但是不好的地方在于之后就不能再恢復(fù)動(dòng)畫了。
圖層的speed(2)一個(gè)簡(jiǎn)單的方法是可以利用CAMediaTiming來(lái)暫停圖層本身沫屡。如果把圖層的speed設(shè)置成0饵隙,它會(huì)暫停任何添加到圖層上的動(dòng)畫。類似的沮脖,設(shè)置speed大于1.0將會(huì)快進(jìn)金矛,設(shè)置成一個(gè)負(fù)值將會(huì)倒回動(dòng)畫。
app delegate ? ==> ?self.window.layer.speed = 100;
手動(dòng)動(dòng)畫
timeOffset? --? 讓你手動(dòng)控制動(dòng)畫進(jìn)程(speed=0勺届,禁用動(dòng)畫的自動(dòng)播放)timeOffset來(lái)顯示動(dòng)畫序列驶俊。
緩沖
Core Animation使用緩沖來(lái)使動(dòng)畫移動(dòng)更平滑更自然
動(dòng)畫速度
velocity = change / time? --? 對(duì)于這種恒定速度的動(dòng)畫我們稱之為“線性步調(diào)”,而且從技術(shù)的角度而言這也是實(shí)現(xiàn)動(dòng)畫最簡(jiǎn)單的方式免姿,但也是完全不真實(shí)的一種效果饼酿。
那么我們?nèi)绾卧趧?dòng)畫中實(shí)現(xiàn)這種加速度呢?一種方法是使用物理引擎來(lái)對(duì)運(yùn)動(dòng)物體的摩擦和動(dòng)量來(lái)建模养泡,然而這會(huì)使得計(jì)算過(guò)于復(fù)雜嗜湃。我們稱這種類型的方程為緩沖函數(shù),幸運(yùn)的是澜掩,Core Animation內(nèi)嵌了一系列標(biāo)準(zhǔn)函數(shù)提供給我們使用购披。
CAMediaTimingFunction
緩沖方程式 = CAAnimation(timingFunction),隱式動(dòng)畫 CATransaction(setAnimationTimingFunction)
(1) +timingFunctionWithName:
kCAMediaTimingFunctionLinear? --? 為nil時(shí)默認(rèn)肩榕,創(chuàng)建了一個(gè)線性的計(jì)時(shí)函數(shù)刚陡,
kCAMediaTimingFunctionEaseIn? --? 創(chuàng)建了一個(gè)慢慢加速然后突然停止的方法(慢->快)
kCAMediaTimingFunctionEaseOut? --? 以一個(gè)全速開始惩妇,然后慢慢減速停止。削弱的效果(快->慢)
kCAMediaTimingFunctionEaseInEaseOut? --? 創(chuàng)建了一個(gè)慢慢加速然后再慢慢減速的過(guò)程筐乳。這是現(xiàn)實(shí)世界大多數(shù)物體移動(dòng)的方式歌殃,也是大多數(shù)動(dòng)畫來(lái)說(shuō)最好的選擇。實(shí)際上當(dāng)使用UIView的動(dòng)畫方法時(shí)蝙云,是默認(rèn)的氓皱,創(chuàng)建CAAnimation的時(shí)候,就需要手動(dòng)設(shè)置它了勃刨。
kCAMediaTimingFunctionDefault? --? 加速和減速的過(guò)程都稍微有些慢波材。創(chuàng)建顯式的CAAnimation它并不是默認(rèn)選項(xiàng)
UIView的動(dòng)畫緩沖
UIViewanimateWithDuration: options: animations:
給options參數(shù):UIViewAnimationOptionCurveEaseInOut,EaseIn身隐,EaseOut廷区,Linear
緩沖和關(guān)鍵幀動(dòng)畫
CAKeyframeAnimation有一個(gè)NSArray類型的timingFunctions屬性,我們可以用它來(lái)對(duì)每次動(dòng)畫的步驟指定不同的計(jì)時(shí)函數(shù)贾铝。但是指定函數(shù)的個(gè)數(shù)一定要等于keyframes數(shù)組的count - 1隙轻,因?yàn)樗敲枋?b>每一幀之間動(dòng)畫速度的函數(shù)。
自定義緩沖函數(shù)
+functionWithControlPoints::::垢揩,有四個(gè)浮點(diǎn)參數(shù)
三次貝塞爾曲線? --? 4個(gè)點(diǎn)玖绿,起點(diǎn)終點(diǎn),兩個(gè)控制點(diǎn)在外(控制了曲線的形狀)
CAMediaTimingFunction函數(shù)的主要原則在于它把輸入的時(shí)間轉(zhuǎn)換成起點(diǎn)和終點(diǎn)之間成比例的改變水孩。
CAMediaTimingFunction? --? -getControlPointAtIndex:values:? 用來(lái)檢索曲線的點(diǎn)镰矿,但是使用它我們可以找到標(biāo)準(zhǔn)緩沖函數(shù)的點(diǎn),然后用?UIBezierPath?和?CAShapeLayer?來(lái)把它畫出來(lái)俘种。
更加復(fù)雜的動(dòng)畫曲線
(1)用CAKeyframeAnimation創(chuàng)建一個(gè)動(dòng)畫,然后分割成幾個(gè)步驟绝淡,每個(gè)小步驟使用自己的計(jì)時(shí)函數(shù)
(2)使用定時(shí)器逐幀更新實(shí)現(xiàn)動(dòng)畫
基于關(guān)鍵幀的緩沖
為了使用關(guān)鍵幀實(shí)現(xiàn)反彈動(dòng)畫宙刘,我們需要在緩沖曲線中對(duì)每一個(gè)顯著的點(diǎn)創(chuàng)建一個(gè)關(guān)鍵幀(在這個(gè)情況下,關(guān)鍵點(diǎn)也就是每次反彈的峰值)牢酵,然后應(yīng)用緩沖函數(shù)把每段曲線連接起來(lái)悬包。同時(shí),我們也需要通過(guò)keyTimes來(lái)指定每個(gè)關(guān)鍵幀的時(shí)間偏移馍乙,由于每次反彈的時(shí)間都會(huì)減少布近,于是關(guān)鍵幀并不會(huì)均勻分布。
用緩沖函數(shù)來(lái)把任何簡(jiǎn)單的屬性動(dòng)畫轉(zhuǎn)換成關(guān)鍵幀動(dòng)畫呢丝格,下面我們來(lái)實(shí)現(xiàn)它撑瞧。
流程自動(dòng)化
用直線來(lái)拼接這些曲線(也就是線性緩沖)
(1)自動(dòng)把任意屬性動(dòng)畫分割成多個(gè)關(guān)鍵幀
(2)用一個(gè)數(shù)學(xué)函數(shù)表示彈性動(dòng)畫,使得可以對(duì)幀做遍歷
(1) 我們需要復(fù)制Core Animation的插值機(jī)制显蝌。這是一個(gè)傳入起點(diǎn)和終點(diǎn)预伺,然后在這兩個(gè)點(diǎn)之間指定時(shí)間點(diǎn)產(chǎn)出一個(gè)新點(diǎn)的機(jī)制。
value = (endValue – startValue) × time + startValue;
基于定時(shí)器的動(dòng)畫
CAMediaTimingFunction? --? 它是一個(gè)通過(guò)控制動(dòng)畫緩沖來(lái)模擬物理效果例如加速或者減速來(lái)增強(qiáng)現(xiàn)實(shí)感的東西,
定時(shí)幀
我們之前提到過(guò)iOS按照每秒60次刷新屏幕酬诀,然后CAAnimation計(jì)算出需要展示的新的幀脏嚷,然后在每次屏幕更新的時(shí)候同步繪制上去,CAAnimation最機(jī)智的地方在于每次刷新需要展示的時(shí)候去計(jì)算插值和緩沖瞒御。
NSTimer
需要確切地知道NSTimer是如何工作的父叙。iOS上的每個(gè)線程都管理了一個(gè)NSRunloop,字面上看就是通過(guò)一個(gè)循環(huán)來(lái)完成一些任務(wù)列表肴裙。但是對(duì)主線程趾唱,這些任務(wù)包含如下幾項(xiàng):
(1)處理觸摸事件
(2)發(fā)送和接受網(wǎng)絡(luò)數(shù)據(jù)包
(3)執(zhí)行使用gcd的代碼
(4)處理計(jì)時(shí)器行為
(5)屏幕重繪
當(dāng)你設(shè)置一個(gè)NSTimer,他會(huì)被插入到當(dāng)前任務(wù)列表中践宴,然后直到指定時(shí)間過(guò)去之后才會(huì)被執(zhí)行鲸匿。但是何時(shí)啟動(dòng)定時(shí)器并沒(méi)有一個(gè)時(shí)間上限,而且它只會(huì)在列表中上一個(gè)任務(wù)完成之后開始執(zhí)行阻肩。這通常會(huì)導(dǎo)致有幾毫秒的延遲带欢,但是如果上一個(gè)任務(wù)過(guò)了很久才完成就會(huì)導(dǎo)致延遲很長(zhǎng)一段時(shí)間。
屏幕重繪的頻率是一秒鐘六十次烤惊,但是和定時(shí)器行為一樣乔煞,如果列表中上一個(gè)執(zhí)行了很長(zhǎng)時(shí)間,它也會(huì)延遲柒室。這些延遲都是一個(gè)隨機(jī)值渡贾,于是就不能保證定時(shí)器精準(zhǔn)地一秒鐘執(zhí)行六十次。>>有時(shí)候發(fā)生在屏幕重繪之后雄右,這就會(huì)使得更新屏幕會(huì)有個(gè)延遲空骚,看起來(lái)就是動(dòng)畫卡殼了。有時(shí)候定時(shí)器會(huì)在屏幕更新的時(shí)候執(zhí)行兩次擂仍,于是動(dòng)畫看起來(lái)就跳動(dòng)了囤屹。
我們可以通過(guò)一些途徑來(lái)優(yōu)化:
(1)我們可以用CADisplayLink讓更新頻率嚴(yán)格控制在每次屏幕刷新之后。
(2)基于真實(shí)幀的持續(xù)時(shí)間而不是假設(shè)的更新頻率來(lái)做動(dòng)畫逢渔。
(3)調(diào)整動(dòng)畫計(jì)時(shí)器的run loop模式肋坚,這樣就不會(huì)被別的事件干擾。
CADisplayLink
CADisplayLink是CoreAnimation提供的另一個(gè)類似于NSTimer的類肃廓,它總是在屏幕完成一次更新之前啟動(dòng)智厌,它的接口設(shè)計(jì)的和NSTimer很類似,所以它實(shí)際上就是一個(gè)內(nèi)置實(shí)現(xiàn)的替代盲赊,但是和timeInterval以秒為單位不同铣鹏,CADisplayLink有一個(gè)整型的frameInterval屬性,指定了間隔多少幀之后才執(zhí)行角钩。默認(rèn)值是1吝沫,意味著每次屏幕更新之前都會(huì)執(zhí)行一次呻澜。但是如果動(dòng)畫的代碼執(zhí)行起來(lái)超過(guò)了六十分之一秒,你可以指定frameInterval = 2惨险,就是說(shuō)動(dòng)畫每隔一幀執(zhí)行一次(一秒鐘30幀)或者3羹幸,也就是一秒鐘20次,等等辫愉。
用CADisplayLink而不是NSTimer栅受,會(huì)保證幀率足夠連續(xù),使得動(dòng)畫看起來(lái)更加平滑恭朗,但即使CADisplayLink也不能保證每一幀都按計(jì)劃執(zhí)行屏镊,一些失去控制的離散的任務(wù)或者事件(例如資源緊張的后臺(tái)程序)可能會(huì)導(dǎo)致動(dòng)畫偶爾地丟幀。當(dāng)使用NSTimer的時(shí)候痰腮,一旦有機(jī)會(huì)計(jì)時(shí)器就會(huì)開啟而芥,但是CADisplayLink卻不一樣:如果它丟失了幀,直接忽略它們膀值,然后在下一次更新的時(shí)候接著運(yùn)行棍丐。
計(jì)算幀的持續(xù)時(shí)間
需要處理一幀的時(shí)間超出了預(yù)期的六十分之一秒。由于我們不能夠計(jì)算出一幀真實(shí)的持續(xù)時(shí)間沧踏,所以需要手動(dòng)測(cè)量歌逢。我們可以在每幀開始刷新的時(shí)候用CACurrentMediaTime()記錄當(dāng)前時(shí)間,然后和上一幀記錄的時(shí)間去比較翘狱。
Run Loop 模式
注意到當(dāng)創(chuàng)建CADisplayLink的時(shí)候秘案,我們需要指定一個(gè)run loop和run loop mode,對(duì)于run loop來(lái)說(shuō)潦匈,我們就使用了主線程的run loop阱高,因?yàn)槿魏斡脩艚缑娴母露夹枰谥骶€程執(zhí)行,但是模式的選擇就并不那么清楚了茬缩,每個(gè)添加到run loop的任務(wù)都有一個(gè)指定了優(yōu)先級(jí)的模式讨惩,為了保證用戶界面保持平滑,iOS會(huì)提供和用戶界面相關(guān)任務(wù)的優(yōu)先級(jí)寒屯,而且當(dāng)UI很活躍的時(shí)候的確會(huì)暫停一些別的任務(wù)。
一個(gè)典型的例子就是當(dāng)是用UIScrollview滑動(dòng)的時(shí)候黍少,重繪滾動(dòng)視圖的內(nèi)容會(huì)比別的任務(wù)優(yōu)先級(jí)更高寡夹,所以標(biāo)準(zhǔn)的NSTimer和網(wǎng)絡(luò)請(qǐng)求就不會(huì)啟動(dòng),一些常見(jiàn)的run loop模式如下:
(1)NSDefaultRunLoopMode ? ? ?-- ? 標(biāo)準(zhǔn)優(yōu)先級(jí)
(2)NSRunLoopCommonModes -- ?高優(yōu)先級(jí)
(3)UITrackingRunLoopMode ? ?-- ?用于UIScrollView和別的控件的動(dòng)畫
在我們的例子中厂置,我們是用了NSDefaultRunLoopMode菩掏,但是不能保證動(dòng)畫平滑的運(yùn)行,所以就可以用NSRunLoopCommonModes來(lái)替代昵济。但是要小心智绸,因?yàn)槿绻麆?dòng)畫在一個(gè)高幀率情況下運(yùn)行野揪,你會(huì)發(fā)現(xiàn)一些別的類似于定時(shí)器的任務(wù)或者類似于滑動(dòng)的其他iOS動(dòng)畫會(huì)暫停,直到動(dòng)畫結(jié)束瞧栗。
同樣可以同時(shí)對(duì)CADisplayLink指定多個(gè)run loop模式斯稳,于是我們可以同時(shí)加入NSDefaultRunLoopMode和UITrackingRunLoopMode來(lái)保證它不會(huì)被滑動(dòng)打斷,也不會(huì)被其他UIKit控件動(dòng)畫影響性能迹恐,像這樣:
self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];?
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
和CADisplayLink類似挣惰,NSTimer同樣也可以使用不同的run loop模式配置,通過(guò)別的函數(shù)殴边,而不是+scheduledTimerWithTimeInterval:構(gòu)造器
物理模擬
即使使用了基于定時(shí)器的動(dòng)畫來(lái)復(fù)制第10章中關(guān)鍵幀的行為憎茂,但還是會(huì)有一些本質(zhì)上的區(qū)別: ? ? <1> 在關(guān)鍵幀的實(shí)現(xiàn)中,我們提前計(jì)算了所有幀锤岸,<2> 但是在新的解決方案中竖幔,我們實(shí)際上實(shí)在按需要在計(jì)算。意義在于我們可以根據(jù)用戶輸入實(shí)時(shí)修改動(dòng)畫的邏輯是偷,或者和別的實(shí)時(shí)動(dòng)畫系統(tǒng)例如物理引擎進(jìn)行整合藏研。
Chipmunk
當(dāng)前基于緩沖的彈性動(dòng)畫? ==>? 真實(shí)的重力模擬效果
(1)cpSpace- 這是所有的物理結(jié)構(gòu)體的容器。它有一個(gè)大小和一個(gè)可選的重力矢量
(2)cpBody- 它是一個(gè)固態(tài)無(wú)彈力的剛體源织。它有一個(gè)坐標(biāo)险领,以及其他物理屬性,例如質(zhì)量戒职,運(yùn)動(dòng)和摩擦系數(shù)等等栗恩。
(3)cpShape- 它是一個(gè)抽象的幾何形狀,用來(lái)檢測(cè)碰撞洪燥】某樱可以給結(jié)構(gòu)體添加一個(gè)多邊形,而且cpShape有各種子類來(lái)代表不同形狀的類型捧韵。
性能調(diào)優(yōu)
CPU所做的工作都在軟件層面市咆,而GPU在硬件層面。我們可以用軟件(使用CPU)做任何事情再来,但是對(duì)于圖像處理蒙兰,通常用硬件會(huì)更快,
Core Animation處在iOS的核心地位:應(yīng)用內(nèi)和應(yīng)用間都會(huì)用到它芒篷。
動(dòng)畫和屏幕上組合的圖層實(shí)際上被一個(gè)單獨(dú)的進(jìn)程管理搜变,而不是你的應(yīng)用程序。這個(gè)進(jìn)程就是所謂的渲染服務(wù)针炉。BackBoard
當(dāng)運(yùn)行一段動(dòng)畫時(shí)候挠他,這個(gè)過(guò)程會(huì)被四個(gè)分離的階段被打破:
(1)布局- 這是準(zhǔn)備你的視圖/圖層的層級(jí)關(guān)系,以及設(shè)置圖層屬性(位置篡帕,背景色殖侵,邊框等等)的階段贸呢。
(2)顯示- 這是圖層的寄宿圖片被繪制的階段。繪制有可能涉及你的-drawRect:和-drawLayer:inContext:方法的調(diào)用路徑拢军。
(3)準(zhǔn)備- 這是Core Animation準(zhǔn)備發(fā)送動(dòng)畫數(shù)據(jù)到渲染服務(wù)的階段楞陷。這同時(shí)也是Core Animation將要執(zhí)行一些別的事務(wù)例如解碼動(dòng)畫過(guò)程中將要顯示的圖片的時(shí)間點(diǎn)。
(4)提交- 這是最后的階段朴沿,Core Animation打包所有圖層和動(dòng)畫屬性猜谚,然后通過(guò)IPC(內(nèi)部處理通信)發(fā)送到渲染服務(wù)進(jìn)行顯示。
一旦打包的圖層和動(dòng)畫到達(dá)渲染服務(wù)進(jìn)程赌渣,他們會(huì)被反序列化來(lái)形成另一個(gè)叫做渲染樹的圖層樹魏铅。使用這個(gè)樹狀結(jié)構(gòu),渲染服務(wù)對(duì)動(dòng)畫的每一幀做出如下工作:
(1)對(duì)所有的圖層屬性計(jì)算中間值坚芜,設(shè)置OpenGL幾何形狀(紋理化的三角形)來(lái)執(zhí)行渲染
(2)在屏幕上渲染可見(jiàn)的三角形
所以一共有六個(gè)階段览芳;最后兩個(gè)階段在動(dòng)畫過(guò)程中不停地重復(fù)。前五個(gè)階段都在軟件層面處理(通過(guò)CPU)鸿竖,只有最后一個(gè)被GPU執(zhí)行沧竟。而且,你真正只能控制前兩個(gè)階段:布局和顯示缚忧。Core Animation框架在內(nèi)部處理剩下的事務(wù)悟泵,你也控制不了它。
在布局和顯示階段闪水,你可以決定哪些由CPU執(zhí)行糕非,哪些交給GPU去做。
GPU相關(guān)的操作
寬泛的說(shuō)球榆,大多數(shù)CALayer的屬性都是用GPU來(lái)繪制朽肥。
有一些事情會(huì)降低(基于GPU)圖層繪制,比如:
(1)太多的幾何結(jié)構(gòu) - 這發(fā)生在需要太多的三角板來(lái)做變換持钉,以應(yīng)對(duì)處理器的柵格化的時(shí)候衡招。
(2)重繪 - 主要由重疊的半透明圖層引起。GPU的填充比率(用顏色填充像素的比率)是有限的每强,所以需要避免重繪(每一幀用相同的像素填充多次)的發(fā)生始腾。
(3)離屏繪制 - 這發(fā)生在當(dāng)不能直接在屏幕上繪制,并且必須繪制到離屏圖片的上下文中的時(shí)候空执。離屏繪制發(fā)生在基于CPU或者是GPU的渲染窘茁,或者是為離屏圖片分配額外內(nèi)存,以及切換繪制上下文脆烟,這些都會(huì)降低GPU性能。對(duì)于特定圖層效果的使用房待,比如圓角邢羔,圖層遮罩驼抹,陰影或圖層光柵化都會(huì)強(qiáng)制Core Animation提前渲染圖層的離屏繪制。
(4)過(guò)大的圖片 - 如果視圖繪制超出GPU支持的2048x2048或者4096x4096尺寸的紋理拜鹤,就必須要用CPU在圖層每次顯示之前對(duì)圖片預(yù)處理框冀,同樣也會(huì)降低性能。
CPU相關(guān)的操作
發(fā)生在動(dòng)畫開始之前敏簿,不會(huì)影響到幀率明也,會(huì)延遲動(dòng)畫開始的時(shí)間。
以下CPU的操作都會(huì)延遲動(dòng)畫的開始時(shí)間: ?-- ?視圖懶加載惯裕,圖片繪制懶解碼
(1)布局計(jì)算 - 如果你的視圖層級(jí)過(guò)于復(fù)雜温数,當(dāng)視圖呈現(xiàn)或者修改的時(shí)候,計(jì)算圖層幀率就會(huì)消耗一部分時(shí)間蜻势。特別是使用iOS6的自動(dòng)布局機(jī)制尤為明顯撑刺,它應(yīng)該是比老版的自動(dòng)調(diào)整邏輯加強(qiáng)了CPU的工作。
(2)視圖惰性加載 - iOS只會(huì)當(dāng)視圖控制器的視圖顯示到屏幕上時(shí)才會(huì)加載它握玛。
(3)Core Graphics繪制 - 如果對(duì)視圖實(shí)現(xiàn)了-drawRect:方法够傍,或者CALayerDelegate的-drawLayer:inContext:方法,那么在繪制任何東西之前都會(huì)產(chǎn)生一個(gè)巨大的性能開銷挠铲。為了支持對(duì)圖層內(nèi)容的任意繪制冕屯,Core Animation必須創(chuàng)建一個(gè)內(nèi)存中等大小的寄宿圖片。然后一旦繪制結(jié)束之后拂苹,必須把圖片數(shù)據(jù)通過(guò)IPC傳到渲染服務(wù)器安聘。在此基礎(chǔ)上,Core Graphics繪制就會(huì)變得十分緩慢醋寝,所以在一個(gè)對(duì)性能十分挑剔的場(chǎng)景下這樣做十分不好搞挣。
(4) 解壓圖片 - PNG或者JPEG壓縮之后的圖片文件會(huì)比同質(zhì)量的位圖小得多。但是在圖片繪制到屏幕上之前音羞,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長(zhǎng) x 4個(gè)字節(jié))囱桨。為了節(jié)省內(nèi)存,iOS通常直到真正繪制的時(shí)候才去解碼圖片嗅绰。
IO相關(guān)操作
IO比內(nèi)存訪問(wèn)更慢舍肠,所以如果動(dòng)畫涉及到IO,就是一個(gè)大問(wèn)題窘面。多線程翠语,緩存和投機(jī)加載(提前加載當(dāng)前不需要的資源,但是之后可能需要用到)财边。
測(cè)量肌括,而不是猜測(cè)
真機(jī)測(cè)試,而不是模擬器
另一件重要的事情就是性能測(cè)試一定要用發(fā)布配置酣难,而不是調(diào)試模式谍夭。因?yàn)楫?dāng)用發(fā)布環(huán)境打包的時(shí)候黑滴,編譯器會(huì)引入一系列提高性能的優(yōu)化,例如去掉調(diào)試符號(hào)或者移除并重新組織代碼
保持一致的幀率
為了做到動(dòng)畫的平滑紧索,你需要以60FPS(幀每秒)的速度運(yùn)行袁辈,以同步屏幕刷新速率。通過(guò)基于NSTimer或者CADisplayLink的動(dòng)畫你可以降低到30FPS珠漂,而且效果還不錯(cuò)晚缩,但是沒(méi)辦法通過(guò)Core Animation做到這點(diǎn)。如果不保持60FPS的速率媳危,就可能隨機(jī)丟幀荞彼,影響到體驗(yàn)。
Instruments