支持原創(chuàng),原文地址:www.KentonYu.com
作品集,其實也談不上作品吧。都是一些小 Demo 。我覺得有必要把一些覺得有意思的東西放在這里展示一下。讓大家來吐槽一下。并不斷的改進(jìn)。但是有一個傷透腦經(jīng)的問題,App 開發(fā)并不好展示,特別是 iOS,當(dāng)然有值得展示的上架應(yīng)用就會把 App Store link 放上來,那些小 Demo ,就只能先上錄屏了。
主要知識點
貝塞爾曲線
給定n+1個數(shù)據(jù)點,p0(x0 , y0) ... pn(xn , yn),生成一條曲線,使得該曲線與這些點所連結(jié)的折線相近。在數(shù)學(xué)中,這屬于逼近問題。在幾何中,可以形象地理解為先用折線段連接這些數(shù)據(jù)點,勾勒出圖形的大致輪廓,然后再用光滑的曲線去盡可能接近地擬合這條折線。摘錄來自: “A GUIDE TO IOS ANIMATION”
UIBezierPath,是 UIKit 對 CoreGraphics 的 path (CGPathRef)的封裝。通過 UIBezierPath 可以繪制直線、圓圈、多邊形和貝塞爾曲線。
下面是對 UIBezierPath 的簡單介紹
<pre>
// 以下是四個類方法,用來繪制閉合的特殊路徑
// 矩形
- (UIBezierPath *)bezierPathWithRect:(CGRect)rect
// 圓角矩形
- (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius
// 矩形內(nèi)切圓
- (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect
// 弧形(clockwise 是否順時針繪制)
- (UIBezierPath *)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
</pre>
<pre>
// 實例方法,可以繪制各種自定義的形狀
// 直線
- (void)addLineToPoint:(CGPoint)point
// 弧形線段
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
// 二階貝塞爾曲線
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint
// 三階貝塞爾曲線
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2
</pre>
通過貝塞爾曲線可以繪制任何你想要畫的形狀。我還沒用過三階貝塞爾。這個<a target="_blank" style='color:#00d6cf'>網(wǎng)站</a>可以體驗下貝塞爾曲線。
CALayer
CALayer 類在概念上和 UIView 類似,同樣也是一些被層級關(guān)系樹管理的矩形塊,同樣也可以包含一些內(nèi)容(圖片,文本或者背景色),管理子圖層的位置。它們有一些方法和屬性用來做動畫和變換。和 UIView 最大的不同是 CALayer 不處理用戶的交互( UIView 繼承 UIResponder,CALayer 繼承 NSObject )。
CALayer 和 UIView 主要區(qū)別:
- 處理觸摸事件
- 陰影、圓角、帶顏色的邊框
- 3D 變換
- 非矩形范圍
- 透明遮罩
- 多級非線性動畫
- .....
CALayer 的子類簡單介紹:
- CAShapeLayer:用來繪制各種形狀
- CATextLayer:用來繪制文字
- CATransformLayer:用來構(gòu)造一個層級的3D結(jié)構(gòu)
- CAGradientLayer:用來生成兩種或更多顏色平滑漸變
- CAReplicatorLayer:用來高效地生成許多相似的圖層
- CAScrollLayer:用來實現(xiàn)圖層滑動
- CATiledLayer:為載入大圖造成的性能問題提供一個解決方案,將大圖分解成小片然后將它們單獨(dú)按需載入
- CAEmitterLayer:高性能的粒子引擎,被用來創(chuàng)建實時粒子動畫(煙、火、雨等)
- CAEAGLLayer:The CAEAGLLayer class supports drawing OpenGL content in iPhone applications
- AVPlayerLayer:用來在iOS上播放視頻的,是 MPMoivePlayer 的底層實現(xiàn),由 AVFoundation 提供
<b>關(guān)于這一部分的具體實踐,后續(xù)會補(bǔ)上。</b>
我們什么時候需要使用 CALayer:
- 開發(fā)同時可以運(yùn)行在 MAC OS 上的跨平臺應(yīng)用
- 使用多種 CALayer 的子類
- 做一些對性能要求很高的工作
drawRect && drawLayer
CALayer 有一個寄宿圖,可以通過 contents 屬性來賦值:
<pre>
layer.contents = (__bridge id)image.CGImage;
</pre>
當(dāng)用代碼的方式來處理寄宿圖的時候,一定要記住要手動的設(shè)置圖層的contentsScale屬性,否則,你的圖片在Retina設(shè)備上就顯示得不正確啦。代碼如下:
<pre>
layer.contentsScale = [UIScreen mainScreen].scale;
</pre>
以上是對寄宿圖的簡單介紹,下面就介紹主題 <code>- drawRect:</code>。
給 contents 賦值(CGImage)并不是唯一設(shè)置寄宿圖的方法,我們也可以通過用 Core Graphics 直接繪制寄宿圖,能夠通過繼承 UIView 并實現(xiàn)<code>- drawRect:</code>來自定義繪制。
<code>- drawRect:</code> 方法沒有默認(rèn)實現(xiàn),因為對 UIView 來說,寄宿圖并不是必須的。如果 UIView 檢測到 <code>- drawRect:</code> 方法被調(diào)用了,它就會為視圖分配一個寄宿圖,這個寄宿圖的大小的尺寸等于視圖大小乘以 contentScale 的積。
<b>所以在不需要使用寄宿圖的時候,就不要創(chuàng)建<code>- drawRect:</code>方法,會導(dǎo)致 CPU 和內(nèi)存資源的浪費(fèi),因此在沒有自定義繪制任務(wù)的 UIView 子類中不要寫一個空的<code>- drawRect:</code>方法</b>
在視圖顯示到屏幕上時,<code>- drawRect:</code>會被自動調(diào)用,并且當(dāng)一些表現(xiàn)效果的屬性值被修改時,一些視圖類型也會被重繪(如 bounds 屬性)。雖然<code>- drawRect:</code>是 UIView 的方法,但是還是底層的 CALayer 進(jìn)行了重繪的操作并保存了產(chǎn)生的寄宿圖。
CALayer 中設(shè)置寄宿圖的過程:首先 CALayer 會請求它的 CALayerDelegate 代理給它一個寄宿圖來顯示。它通過下面這個方法來實現(xiàn)獲取。
<pre>
- (void)displayLayer:(CALayerCALayer *)layer;
</pre>
如果代理不實現(xiàn)這個方法, CALayer 會轉(zhuǎn)而嘗試調(diào)用下面這個方法:
<pre>
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
</pre>
在調(diào)用這個方法之前,CALayer 創(chuàng)建了一個合適尺寸的空寄宿圖(尺寸由 bounds 和 contentsScale 決定)和一個 CoreGraphics 的繪制上下文環(huán)境,為繪制寄宿圖做準(zhǔn)備,他作為 ctx 參數(shù)傳入。
但是在使用這個方法時,不同于 UIView ,CALayer 不會自動重繪它的內(nèi)容,需要手動調(diào)用 <code>- display</code>。
總的來說,當(dāng)使用寄宿了視圖的圖層的時候,不必實現(xiàn)<code>- displayLayer:</code>和<code>- drawLayer: inContext:</code>方法來繪制寄宿圖。通常做法是實現(xiàn) UIView 的<code>- drawRect:</code>方法,UIView 會做完剩下的工作,包括在需要重繪的時候調(diào)用<code>- display</code>方法。
這是一篇關(guān)于 drawRect 的博文《內(nèi)存惡鬼 drawRect》 ,看完可以學(xué)到更多關(guān)于 drawRect 的知識。
NSTimer && CADisplayLink
NSTimer 是如何工作的?
當(dāng)設(shè)置一個 NSTimer 時,NSTimer 會被插入到當(dāng)前的 NSRunloop 中,然后直到指定的時間過去之后才會被執(zhí)行。但是什么時候啟動定時器并沒有上限,而且只有當(dāng) NSRunloop 的上一個任務(wù)結(jié)束之后才會被執(zhí)行,因此通常會導(dǎo)致不定時的延遲。 NSRunloop 主要爭對主線程,其中包含的任務(wù)有如下幾項:
- 處理觸摸事件
- 發(fā)送和接受網(wǎng)絡(luò)數(shù)據(jù)包
- 執(zhí)行使用 GCD 的代碼
- 處理計時器的行為
- 屏幕重繪
因此屏幕重繪的頻率是60次/秒,但是和定時器一樣,如果上一次重繪執(zhí)行很長的時間,那么也會導(dǎo)致延遲。就不能保證定時器精準(zhǔn)的每一秒執(zhí)行60次。
對于動畫的實現(xiàn)可以通過以下這些途徑進(jìn)行優(yōu)化:
- 可以用 CADisplayLink 讓更新頻率嚴(yán)格控制在每次屏幕刷新之后
- 基于真實幀的持續(xù)時間而不是假設(shè)的更新頻率來做動畫
- 調(diào)整動畫計時器的 RunLoop 模式,這樣就不會被別的事件干擾
用 CADisplayLink 而不是 NSTimer,會保證幀率足夠連續(xù),使得動畫看起來更加平滑,但即使 CADisplayLink 也不能保證每一幀都按計劃執(zhí)行,一些失去控制的離散的任務(wù)或者事件(例如資源緊張的后臺程序)可能會導(dǎo)致動畫偶爾地丟幀。
無論是使用 NSTimer 還是 CADisplayLink,我們?nèi)匀恍枰幚硪粠臅r間超出了預(yù)期的1/60秒。由于我們不能夠計算出一幀真實的持續(xù)時間,所以需要手動測量。我們可以在每幀開始刷新的時候用 CACurrentMediaTime() 記錄當(dāng)前時間,然后和上一幀記錄的時間去比較。
總結(jié)
在這個 Demo 里主要涉及了以上幾個知識點,通過 CAShapeLayer 來繪制整個臺子,然后通過實現(xiàn) <code>- drawLayer: inContext:</code> 實現(xiàn)橢圓進(jìn)度條的效果(在淘寶彩排H5里有個顏色漸變的過程,我沒有實現(xiàn))。通過這個小 Demo,鞏固了之前 iOS 動畫學(xué)習(xí)的知識,感覺寫動畫是一個很有趣的事情。當(dāng)然不得不說,這個看似簡單的小游戲,實際做起來邏輯還是有點復(fù)雜的~