高質(zhì)量的圖形是用戶界面的關(guān)鍵部分. 提供良好的圖形不僅使你的APP看起來漂亮, 而且讓你的APP看起來就是iOS系統(tǒng)的自然延伸. iOS提供兩種繪制高質(zhì)量圖形的方式, 一種是使用OpenGL, 另外一種是使用原生的技術(shù)包括Quartz, Core Animation, UIKit; 本系列文章只涉及原生技術(shù), 如果想要學(xué)習(xí)OpenGL繪圖, 請看OpenGL ES Programming Guide
Quartz是主要繪圖接口, 它支持基于路徑的繪制, 抗鋸齒渲染, 漸變填充模式, 圖片, 顏色, 坐標(biāo)變換; 和PDF文檔的創(chuàng)建, 解析和顯示. UIKit是OC類, 可以對線條, 圖片, 顏色進(jìn)行操控. Core Animation在底層支持UIKit中view的屬性的動(dòng)畫變動(dòng), 也會經(jīng)常用來創(chuàng)建自定義動(dòng)畫.
本文是對iOS繪圖過程的一個(gè)概述, 以及iOS原生繪圖技術(shù)的具體講解, 同時(shí)還會對如何優(yōu)化iOS繪圖的一些提示和指導(dǎo).
注意:不是所有的UIKit類是線程安全的, 在非主線程中進(jìn)行圖形繪制時(shí), 先確認(rèn)該類是否線程安全.
UIKit繪圖
在iOS系統(tǒng)中, 不管通過何種(OpenGL, Quartz, UIKit或者Core Animation)繪圖技術(shù)往屏幕繪制內(nèi)容是需要通過類UIView
或者其子類來進(jìn)行的. 視圖定義了屏幕繪圖的部分, 如果使用系統(tǒng)變種的view來繪制, 將會自動(dòng)完成, 如果你使用自定義view, 那么你需要自己編寫繪圖代碼. 下面的內(nèi)容描述了iOS原生繪圖技術(shù)的一些概念.
除了直接在屏幕上繪制內(nèi)容, UIKit還允許你將內(nèi)容繪制到屏幕的bitmap/PDF等圖形上下文. 當(dāng)你在屏幕外上下文繪制時(shí), 你此時(shí)不是在view中繪制, 所以有些繪畫概念(view的繪制周期)對此不管用.(除非你獲得該圖像并將其繪制在圖像視圖或類似圖像中)
視圖繪制周期
UIView
的子類的基本繪畫模型是按需更新內(nèi)容. 類UIView
使得更新過程很簡單和高效; 然而, UIView會收集你的更新請求然后在合適的時(shí)候?qū)⑵溥f交給到你的繪制代碼.
當(dāng)一個(gè)view第一次顯示或者當(dāng)view中部分需要重繪時(shí), iOS通過調(diào)用drawRect:
來通知view進(jìn)行繪制. 下面有個(gè)操作會觸發(fā)view的更新:
- 當(dāng)view被新加入的view部分遮住, 或者移除這個(gè)部分遮住你的view
- 將之前隱藏的view設(shè)置為顯示(將view的
hidden
屬性設(shè)置NO) - 將view滾動(dòng)到屏幕外, 然后又滾動(dòng)回來屏幕中.
- 顯示地調(diào)用
setNeedDisplay
和setNeedDisplayInRect:
方法
系統(tǒng)的view會自動(dòng)重繪. 對于自定義view, 你必須重寫drawRect:
方法, 自定義view是通過該方法來繪制內(nèi)容的. 在drawRect:
方法中, 你使用原生繪制技術(shù)來繪制圖形, 文字, 圖片, 漸變等可見的UI元素. 在view第一次顯示時(shí), iOS會往drawRect:
傳入一個(gè)rect參數(shù), 該參數(shù)是一個(gè)包含整個(gè)view可見矩形區(qū)域; 在該方法接下來的調(diào)用中, rect參數(shù)可能代表是該view的部分區(qū)域. 為了提高性能, 你應(yīng)該重繪受影響的內(nèi)容.
在調(diào)用drawRect:
方法后, view標(biāo)記為已更新, 然后等下一個(gè)更新周期. 如果你的view顯示的靜態(tài)的內(nèi)容, 那么你只需要關(guān)心因?yàn)闈L動(dòng)和新加入的view導(dǎo)致你的view的可見性的變化.
如果你想改變view的內(nèi)容, 你需要調(diào)用setNeedsDisplay
或setNeedsDisplayInRect:
方法來觸發(fā)view的更新. 比如, 你想在一定時(shí)間內(nèi)對view進(jìn)行多次更新, 那么你可能會創(chuàng)建一個(gè)timer來更新你的view. 你可能因?yàn)轫憫?yīng)用戶交互而更新view的內(nèi)容.
注意:記住不要自己去調(diào)用
drawRect:
方法, 該方法是在屏幕重繪時(shí)由iOS系統(tǒng)內(nèi)置代碼調(diào)用, 另外在其他時(shí)間, 你是無法獲取到graphic Context的, 所以你也沒法進(jìn)行繪制.
iOS中的坐標(biāo)系和圖形繪制
當(dāng)iOSAPP開始繪制時(shí), iOS會通過坐標(biāo)系(coordinate system)將繪制的內(nèi)容在二維空間內(nèi)定位好. 這看起很好理解, 其實(shí)不然, iOSAPP中的內(nèi)容繪制可能碰到不同的坐標(biāo)系.
在iOS中, 繪制是在繪圖上下文中進(jìn)行的. 理論上, 一個(gè)繪圖上文對象用來描述在那里, 又是如何進(jìn)行繪制的. 包括一些基本繪制信息, 比如繪制時(shí)顏色的設(shè)置, 剪輯區(qū)域, 線寬和線的分格, 字體設(shè)置, 合成選擇等等信息.
另外, 如圖2-1所示, 每個(gè)繪圖上下文都有一個(gè)坐標(biāo)系. 更準(zhǔn)確說, 是每個(gè)繪圖上下文包含三個(gè)坐標(biāo)系:
- 繪畫(用戶)坐標(biāo)系, 在執(zhí)行繪畫命令時(shí)使用.
- 視圖坐標(biāo)系, 和view相關(guān)的坐標(biāo)系
- 設(shè)備坐標(biāo)系, 表示屏幕上個(gè)像素
iOS的繪圖框架為具體繪圖目的地(屏幕, bitmap, PDF)創(chuàng)建繪圖上下文, 并且這些繪圖上下文為這些繪圖目的地搭建一個(gè)初始化繪圖坐標(biāo)系統(tǒng). 這個(gè)初始繪圖坐標(biāo)系就是所謂的默認(rèn)坐標(biāo)系, 它是對于view底層坐標(biāo)系的一個(gè)1:1的映射.
每一個(gè)view都有一個(gè)當(dāng)前變換矩陣(CTM), 該矩陣將當(dāng)前繪制坐標(biāo)系中的點(diǎn)映射到視圖坐標(biāo)系中. APP可以通過修改這個(gè)矩陣來改變未來繪圖操作的行為.
每一個(gè)繪圖框架是基于當(dāng)前上下文來構(gòu)建默認(rèn)坐標(biāo)系的, 在iOS中, 有兩種坐標(biāo)系:
- 原點(diǎn)在左上角的坐標(biāo)系(ULO), x軸正向是往右延伸, y軸正向是往下延伸. 該默認(rèn)坐標(biāo)系被UIKit和Core Animation使用
-
原點(diǎn)在左下角的坐標(biāo)系(LLO), x軸正向是往右延伸, y軸正向是往上延伸. 該默認(rèn)坐標(biāo)系被Core Graphic使用
注意:OS X系統(tǒng)中的默認(rèn)坐標(biāo)系是LLO型. 另外APPKit支持將坐標(biāo)翻轉(zhuǎn)成ULO型.
在調(diào)用drawRect:
前, UIKit會先讓繪圖上下文有效, 之后搭建默認(rèn)坐標(biāo)系來往屏幕上繪制內(nèi)容. 在drawRect:
方法中, 可以對graphic Context進(jìn)行一些圖形狀態(tài)參數(shù)的設(shè)置(比如填充顏色), 該操作不需要顯示引用graphic context.
點(diǎn)(point) VS 像素(pixel)
在iOS中, 繪圖代碼中的坐標(biāo)點(diǎn)和物理設(shè)備中的像素是有區(qū)別的. 在使用原生技術(shù)進(jìn)行繪圖時(shí), 不管時(shí)繪圖坐標(biāo)系還是視圖坐標(biāo)系中的空間是邏輯空間, 單位是點(diǎn). 使用邏輯坐標(biāo)系的優(yōu)點(diǎn)是, 和設(shè)備坐標(biāo)系空間解耦了, 設(shè)備坐標(biāo)系被系統(tǒng)框架用來管理屏幕上的像素.
系統(tǒng)會自動(dòng)將視圖坐標(biāo)系中的點(diǎn)映射到設(shè)備坐標(biāo)系中的像素, 這種映射不總是1:1的. 基于這種特性, 你需要牢記以下事實(shí):
一點(diǎn)不總是等于一個(gè)物理像素
使用點(diǎn)(邏輯坐標(biāo)系)的目地是提供一個(gè)和設(shè)備無關(guān)的一致性的大小輸出. 大多數(shù)情況下, 不需要考慮一點(diǎn)的實(shí)際大小. 使用點(diǎn), 可以提供一種你可以用來設(shè)置view中內(nèi)容的大小, 位置的相對一致性的尺度(scale). 一點(diǎn)到底映射多少像素是有底層系統(tǒng)框架決定的. 比如, 在高分辨率屏幕中, 寬度為1點(diǎn)的線條在屏幕上顯示的是線寬為兩個(gè)像素的線條. 結(jié)果是, 如果在兩個(gè)相似的設(shè)備上繪制的內(nèi)容相同, 其中一個(gè)設(shè)備是高分辨率的, 那么這兩個(gè)設(shè)備上的內(nèi)容看起來會大致相同.
注意:在繪制和打印PDF時(shí)的上下文中, Core Graphic定義的"點(diǎn)"使用工業(yè)標(biāo)準(zhǔn)長度是1/32英寸.
在iOS中, UIScreen
,UIView
, UIImage
和CALayer
這些類都會有個(gè)屬性來獲取縮放因子(scale), scale用來描述點(diǎn)和實(shí)際像素的關(guān)系. 比如, 每個(gè)UIKit視圖都會有個(gè)contentScaleFactor
屬性. 在普通屏幕上, scale一般為1.0. 如果是高分辨率屏幕, scale通常為2.0. 或許在未來, 有值更高的scale. (iOS4.0之前的版本, 你應(yīng)該認(rèn)為scale默認(rèn)為1.0)
原生繪圖技術(shù), 像Core Graphic, 會自動(dòng)考慮了scale的值, 你不需要關(guān)注這個(gè). 比如, 某個(gè)view實(shí)現(xiàn)了drawRect:
方法, UIKit自動(dòng)設(shè)置view的scale為屏幕的scale. 另外, UIKit自動(dòng)修改graphic context的變換矩陣來適配view的scale. 因此, drawRect:
方法中繪制的內(nèi)容已經(jīng)進(jìn)行了合適的scale.
因?yàn)檫@種底層的自動(dòng)映射, 當(dāng)你寫繪圖代碼時(shí), 不需要考慮像素. 但存在需要考慮點(diǎn)和像素的映射關(guān)系的情況, 比如在高分辨率屏幕上顯示高分辨率圖片或者在低分率屏幕上繪制圖形時(shí)避免縮放失真.
在iOS系統(tǒng)中, 當(dāng)你在屏幕上繪制內(nèi)容時(shí), 繪圖子系統(tǒng)會使用一種反鋸齒的技術(shù)在底分辨率屏幕上顯示高清圖形. 舉個(gè)例子, 你在白色屏幕上繪制一條黑色的實(shí)線, 當(dāng)該線條剛好要顯示在像素點(diǎn)上時(shí), 屏幕會使用一系列像素顯示一條黑色的線, 如果該線條要顯示在兩個(gè)像素之間的話, 那么屏幕會顯示兩條灰色像素線, 如下圖2-3所示.
位置是整數(shù)點(diǎn)時(shí), 會顯示在像素點(diǎn)中間. 比如你要畫一條從點(diǎn)(1.0,1.0)到點(diǎn)(1.0,10.0),寬度為一個(gè)像素點(diǎn)的垂直線, 你會得到一條模糊的灰色線條. 如果線的寬度為2個(gè)像素點(diǎn), 你會得到一條黑色的實(shí)線. 基于這種規(guī)則, 寬度為奇數(shù)個(gè)像素的線比寬度為偶數(shù)個(gè)像素點(diǎn)的線看起來更加柔和, 除非你改動(dòng)了他們的位置, 是線條剛好坐落在像素點(diǎn)上.
scale因子的作用是決定一個(gè)點(diǎn)包含多少個(gè)像素點(diǎn).
在一塊低分辨率的屏幕上(scale為1.0), 單點(diǎn)線寬等于單像素線寬. 為了在繪圖抗鋸齒的垂直/水平的線條, 如果線是寬度為奇數(shù)的像素隐圾,則必須將位置偏移0.5個(gè)點(diǎn)到整數(shù)點(diǎn)位置的兩側(cè)枫攀。如果線是偶數(shù)個(gè)點(diǎn)的寬度,為了避免模糊線冯乘,你則不能這樣做。
在高分率屏幕上(scale因子為2.0), 單點(diǎn)線根本不會進(jìn)行反鋸齒, 因?yàn)樗紦?jù)著兩個(gè)完全的像素點(diǎn)(從-0.5到+0.5). 要繪制一條只覆蓋當(dāng)個(gè)物理像素的線, 你需要設(shè)置線寬為0.5點(diǎn), 并偏移氣味0.25點(diǎn), 上圖2-4展示了這兩種屏幕上的比較.
當(dāng)然, 基于scale因子改變繪圖特性可能會產(chǎn)生不可預(yù)的后果. 在一些設(shè)備上, 1像素寬的線條看起來正常, 但是在高分辨率屏幕上, 線條可能太細(xì), 細(xì)到難以看清楚. 所以, 在改變繪圖特性前要考慮清楚.
獲取繪圖上下文(Graphic Context)
多數(shù)情況下, graphic context已經(jīng)自動(dòng)配置好了, view對象會為你干完這事, 以便drawRect:
方法中的繪圖代碼可以立即執(zhí)行. 作為該配置的一部分工作, 底層UIView
類會創(chuàng)建一個(gè)graphic context(CGContextRef類型)對象.
如果你想在view之外的其他地方(比如, 在往PDF和bitmap文件中繪制內(nèi)容)繪制內(nèi)容, 或者使用Core Graphic方法進(jìn)行繪制時(shí), 你需要自己去獲取graphic context. 該部分內(nèi)容接下來會講到.
如果你獲得更多關(guān)于graphic context的信息, 修改圖形狀態(tài)信息, 和使用graphic contexts來創(chuàng)經(jīng)濟(jì)自定義內(nèi)容, 請看蘋果官文Quartz 2D Programming Guide
在屏幕上繪制
在使用Core Graphic函數(shù)在view中繪圖時(shí), 不論是在drawRect:
或其他地方, 你都要用到graphic context來進(jìn)行繪制(許多繪圖函數(shù)的第一個(gè)參數(shù)都是CGContextRef
類型). 你可以使用UIGraphicsGetCurrentContext
函數(shù)來獲取一個(gè)顯式版本的graphic context, 該對象和方法drawRect:
中獲取的隱式graphic context是同一個(gè)對象. 因?yàn)槭峭粋€(gè)graphic context, 所以這些繪畫函數(shù)是基于ULO類型的坐標(biāo)系.
如果你想使用Core Graphic函數(shù)在UIKit視圖中繪制, 你應(yīng)該使用UIKit中的ULO坐標(biāo)系. 或者你可以先對graphic context的CTM進(jìn)行上下翻轉(zhuǎn)后, 然后使用core graphic中的原生LLO坐標(biāo)系在UIKit視圖中繪制內(nèi)容. 下面章節(jié)中會講到如何翻轉(zhuǎn)坐標(biāo)系.
使用函數(shù)UIGraphicsGetCurrentContext
獲取的對象, 總是當(dāng)前的graphic context. 比如, 你創(chuàng)建了PDF的context, 此時(shí)使用UIGraphicGetCurrentContext
函數(shù)獲取的就是一個(gè)PDF context. 所以在使用Core Graphic函數(shù)進(jìn)行往view中繪制內(nèi)容時(shí), 必須使用UIGraphicGetCurrentContext
函數(shù)來獲取繪圖上下文.
注意:類
UIPrintPageRenderer
聲明了幾個(gè)用來繪制可打印內(nèi)容的方法, 和drawRect:
方法類似, UIKit會值隱式地為這些方法創(chuàng)建一個(gè)graphic context, 這些graphic context建立的坐標(biāo)系是ULO.
在Bitmap和PDF上下文環(huán)境中繪制
UIKit有提供繪制image到bitmap中, 以及繪制PDF文件的功能. 這兩種繪制方式首先都要獲取相應(yīng)的上下文(bitmap context和PDF context). 獲取到的上下文對象會被接下來的繪制過程中一直使用. 當(dāng)你繪制完內(nèi)容后, 需要你調(diào)用其他函數(shù)去關(guān)閉當(dāng)前的上下文.
bitmap和PDF context使用的是UIKit提供的ULO坐標(biāo)系. Core graphic框架中也有繪制bitmap和PDF的函數(shù). 所以, 你直接使用core graphic創(chuàng)建的上下文需要翻轉(zhuǎn)坐標(biāo)系后才能用.
注意:在iOS中, 推薦使用UIKit的方法來繪制bitmap和PDF. 然后, 如果你使用core graphic來繪制也行, 但要記住要調(diào)整坐標(biāo)系
色彩和色彩空間
iOS支持Quartz中全部范圍的色彩空間; 但是, 大多數(shù)APP只需要RGB色彩空間. 因?yàn)閕OS是為帶屏幕的嵌入式硬件設(shè)計(jì)的, 所以RGB色彩空間是最適合使用的.
類UIColor
提供了許多便捷方法來設(shè)置RGB, HSB顏色, 而且還可以使用灰度值. 你使用這種方式創(chuàng)建顏色時(shí)是不需要考慮色彩空間的, 因?yàn)轭?code>UIColor已經(jīng)幫你做好了.
你也可以使用core graphic中的CGContextSetRGBStrokeColor
和CGContextSetRGBFillColor
函數(shù)來創(chuàng)建和設(shè)置顏色. 雖然core graphic框架支持使用色彩空間來創(chuàng)建顏色, 也可以創(chuàng)建自定義顏色, 然后使用這些顏色來繪制圖形, 但是這種方式是不推薦的. 你應(yīng)該使用RGB顏色來繪制.
使用UIKit和Quartz來繪圖
Quartz是iOS系統(tǒng)繪圖技術(shù)的一個(gè)總稱, Core Graphic框架是Quartz的核心, 并且提供了用于繪圖的主要接口(API). 該框架提供了數(shù)據(jù)類型和功能來做以下的活兒:
- Graphic context, 繪圖上下文
- Paths, 路徑
- Image and bitmaps, 圖片和位圖
- Transparency layers, 透明層
- Colors, pattern colors, and color spaces, 顏色晒夹、圖案顏色和顏色空間
- Gradients and shadings, 漸變和陰影
- Fonts, 字體
- PDF content, PDF內(nèi)容
UIKit在基于Quartz上建立了一套, 集中的, 用來圖形操作的類. UIKit不打算設(shè)計(jì)一套全面的繪圖工具, 因?yàn)镃ore Graphic已經(jīng)實(shí)現(xiàn)了這個(gè)功能. UIKit支持和包含以下功能和類:
- UIImage, 用來展示不可修改的圖像
- UIColor, 提供設(shè)備顏色的基礎(chǔ)支持
- UIFont, 提供其他類想要字體信息
- UIScreen, 提供和屏幕相關(guān)的信息
- UIBezierPath, 使用該對象能是你繪制線, 弧, 橢圓和其他一些圖形.
- 使用UIImage來創(chuàng)建JPEG或者PNG圖片
- 將內(nèi)容繪制到bitmap中
- 生成PDF數(shù)據(jù)
- 繪制矩形和剪切繪制區(qū)域
- 獲取和修改當(dāng)前graphic context
想要知道更多關(guān)于UIKit框架的信息, 請看UIKit官方接口文檔UIKit Framework Reference
配置Graphics Context
在調(diào)用drawRect:
之前, view會創(chuàng)建和設(shè)置好繪圖上下文, 該上下文只能在drawRect:
方法中存活. 你可以通過UIGraphicGetCurrentContext
函數(shù)來獲取對該上下文的一個(gè)引用, 該方法會返回一個(gè)CGContextRef
類型的指針, 你可以將該指針傳入到相關(guān)core graphic函數(shù)中來修改上下文的一些狀態(tài). 表1-1列舉了一系列用于修改繪圖上下文狀態(tài)的函數(shù), 同時(shí)還列舉了UIKit中的替代方法.
表2-1 用來修改graphic狀態(tài)的函數(shù)
Graphic state | Core Graphic函數(shù) | 使用UIKit替代 |
---|---|---|
Current transformation martix(CTM) | CGContextRotateCTM CGContextScaleCTM CGContextTranslateCTM CGContextContactCTM | None |
裁剪區(qū)域 | CGContextClipToRect | UIRectClip函數(shù) |
線:寬,連接處,cap,dash(虛線),miter limit(尖角) | CGContextSetLineWidth CGContextSetLineJoin CGContextSetLineCap CGContextSetLineDash CGContextSetMiterLimit | None |
曲線估計(jì)精度 | CGContextSetFlatness | None |
抗鋸齒設(shè)置 | CGContextSetAllowsAntialiasing | None |
透明度(全局) | CGContextSetAlpha | None |
顏色:填充/描邊 | CGContextSetRGBFillColor CGContextSetRGBStrokeColor | UIColor類 |
渲染意圖 | CGContextSetRenderingIntent | None |
色彩空間:填充/描邊 | CGContextSetFillColorSpace CGContextSetStrokeColorSpace | UIColor類 |
文本:font,fontSize,文字間距,文本繪制模式等 | CGContextSetFont CGContextSetFontSize CGContextSetCharacterSpacing | UIFont類 |
混合模式 | CGContextSetBlendMode | UIImage類和方法可以讓你設(shè)置具體的混合模式 |
graphic context使用一個(gè)棧來保存圖形狀態(tài)(graphic state). 當(dāng)Quartz創(chuàng)建graphic context時(shí), 棧時(shí)空的. 使用CGContextSaveGState
函數(shù)來將當(dāng)前圖形狀態(tài)副本push到棧上. 此后, 對圖形狀態(tài)的修改將影響后續(xù)的繪圖操作, 但不影響棧上存儲的副本. 修改完后, 可以使用CGContextRestoreGState函數(shù)將棧上的圖形狀態(tài)pop出棧頂, 返回到以前的圖形狀態(tài). 這種push和pop圖形狀態(tài)方式是回到先前狀態(tài)的快速方式, 并且消除了單獨(dú)撤銷每個(gè)狀態(tài)更改的需要. 這種方式也是恢復(fù)某些圖形狀態(tài)的唯一方式, 比如clipping path.
想要了解更多關(guān)于graphic context和使用context來配置繪制環(huán)境的知識, 請看Quartz 2D Programming Guide中的Graphics Contexts
創(chuàng)建路徑(path)并繪制
路徑(path)是由一系列線段和Bezier曲線組成的基于向量的圖形. UIKit使用UIRectFrame
,UIRectFill
等函數(shù)在view中來繪制簡單的路徑, 如矩形. Core Graphic同樣也有許多創(chuàng)建簡單path(如矩形, 橢圓)的便捷函數(shù).
對于更加復(fù)雜的路徑, 你必須使用UIKit中的類UIBezierPath
來自己創(chuàng)建路徑, 或者使用core graphic框架中的函數(shù)來創(chuàng)建CGPathRef
類型的路徑. 盡管你可以使用任意Api來構(gòu)建沒有圖形上下文的路徑, 但是路徑中的點(diǎn)任然需要引用當(dāng)前坐標(biāo)系(該坐標(biāo)系的方向是ULO或LLO), 并且繪制出該路徑時(shí)需要用到圖形上下文.
繪制路徑時(shí), 你必須具有當(dāng)前上下文. 該上下文可以是自定義view的drawRect:
中的上下文, 也可以是bitmap上下文, 或者PDF上下文. 坐標(biāo)系決定了path的繪制, 在使用UIBezierPath時(shí), 當(dāng)前坐標(biāo)系是ULO方向. 因此, 你如果想在LLO方向的坐標(biāo)系中繪制的話, 會使的結(jié)果大不一樣. 為了獲得最佳結(jié)果裆馒,您應(yīng)該始終指定與用于渲染的圖形上下文的當(dāng)前坐標(biāo)系的原點(diǎn)相關(guān)的點(diǎn).
注意:弧(Arcs)是一種path, 但繪制該弧稍微復(fù)雜點(diǎn). 你如果使用Core Graphic來創(chuàng)建path, 因?yàn)楫?dāng)前坐標(biāo)系是ULO(和Core Graphic的默認(rèn)的LLO上下顛倒了), 所以你畫出的弧會和你預(yù)想的不一樣.
為了在iOS中創(chuàng)建路徑, 推薦使用UIBezierPath來創(chuàng)建path, 而不是CGPath函數(shù). 除非你需要的功能只有Core Graphic中提供, 比如向路徑中添加省略號. 有關(guān)在UIKit中創(chuàng)建路徑, 請看后續(xù)章節(jié)使用UIBezierPath來繪制圖形.
有關(guān)使用UIBezierPath來繪制路徑, 請看使用UIBezierPath來繪制圖形. 更多關(guān)于使用Core Graphic函數(shù)來繪制路徑請看Quartz 2D Programming Guide中的Paths部分.
創(chuàng)建圖案紋理(Patterns), 漸變(Gradients), 陰影(Shadings)
Core Graphic框架包括用于創(chuàng)建圖案紋理, 漸變和陰影的附加功能. 你可以使用這類功能來創(chuàng)建非單色的顏色, 并使用它們來填充你創(chuàng)建的路徑. 圖案(pattern)是由重復(fù)圖像或內(nèi)容組成的. 漸變和陰影提供了不同的方式來創(chuàng)建從顏色到顏色的平滑過渡. 這些內(nèi)容的具體實(shí)現(xiàn)細(xì)節(jié)在文章Quartz 2D Programming Guide可以看到.
自定義坐標(biāo)空間
默認(rèn)情況下, UIKit創(chuàng)建一個(gè)簡單的當(dāng)前轉(zhuǎn)換矩陣, 將點(diǎn)映射到像素上. 雖然你可以在不修改矩陣的情況下完成所有繪圖, 但有時(shí)也會很方便.
當(dāng)你的視圖的drawRect:
方法第一次被調(diào)用時(shí), CTM會被配置好, 使的視圖的origin和坐標(biāo)系的原點(diǎn)重合, x軸正向往右, y軸正向往下. 但是你可以通過縮放(scaling), 旋轉(zhuǎn), 位移等操作來改變CTM, 從而改變底層視圖或窗口的坐標(biāo)系的大小, 方向, 位置.
利用坐標(biāo)變換提高繪圖性能
在view中繪圖時(shí), 改變CTM是一種常用的技術(shù), 因?yàn)檫@樣可以重用path, 也會減少繪畫時(shí)的計(jì)算量. 比如, 如果你希望從點(diǎn)(20, 20)開始繪制正方形, 則可以創(chuàng)建移動(dòng)到點(diǎn)(20,20)的路徑, 然后繪制所需的四條邊完成正方形. 但是, 如果你稍后決定將該正方形移動(dòng)到點(diǎn)(10, 10), 則必須用新的起點(diǎn)重新創(chuàng)建路徑. 因?yàn)閯?chuàng)建路徑是一種相對耗性能的操作, 所以最好創(chuàng)建原點(diǎn)在(0,0)的方塊, 然后需改CTM, 以便將正方形位移到所需的原點(diǎn).
在Core Graphic框架中, 有兩種方式來修改CTM.
- 你可以使用定義在CGContext接口中的函數(shù)直接修改CTM
- 創(chuàng)建一個(gè)CGAffineTransform結(jié)構(gòu)體, 應(yīng)用各種變換后, 將該變換連接到CTM上.
使用仿射變換可以對多種變換組合一起, 然后一次性的應(yīng)用到CTM中, 你還可以評估和反轉(zhuǎn)仿射變換, 并使用這些變換來修改代碼中的點(diǎn), 大小和矩形值. 更多關(guān)于仿射變換的信息請看Quartz 2D Programming Guide和CGAffineTransform Reference部分.
翻轉(zhuǎn)默認(rèn)坐標(biāo)系
坐標(biāo)系翻轉(zhuǎn)在UIKit繪圖中的修改了支持CALayer,以使具有LLO坐標(biāo)系的繪圖環(huán)境與UIKit的默認(rèn)坐標(biāo)系對齊丐怯。如果只使用UIKIT方法和函數(shù)進(jìn)行繪圖喷好,則不需要翻轉(zhuǎn)CTM。但是读跷,如果將Core Graphic或圖像I/O函數(shù)調(diào)用與UIKit調(diào)用混合梗搅,則可能需要翻轉(zhuǎn)CTM。
具體地說效览,如果通過直接調(diào)用Core Graphics函數(shù)來繪制圖像或PDF文檔无切,則繪圖的內(nèi)容在視圖的上下文中將上下顛倒。您必須翻轉(zhuǎn)CTM以正確顯示圖像和頁面丐枉。
若要將繪制的內(nèi)容翻轉(zhuǎn)到Core Graphic上下文哆键,以便在UIKit視圖中顯示時(shí)正確顯示,必須分兩個(gè)步驟修改CTM瘦锹。將原點(diǎn)轉(zhuǎn)換為繪圖區(qū)域的左上角籍嘹,然后應(yīng)用比例尺轉(zhuǎn)換,將y坐標(biāo)修改為-1弯院。這樣做的代碼類似于下面的代碼:
CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);
如果用CGImageRef來創(chuàng)建UIImage對象, UIKit會為你自動(dòng)執(zhí)行翻轉(zhuǎn)轉(zhuǎn)換. 每一個(gè)UIImage背后是有CGImageRef來支持的. 你可以通過類UIIMage
的CGImage
屬性來訪問這個(gè)隱藏的對象. 通過這個(gè)隱藏的對象我可以對圖像做一些特性的處理(Core Graphic有部分針對image的能力是UIKit不具備的). 當(dāng)你對CGImageRef對象做完特殊處理后, 你可以用它在創(chuàng)建UIImage對象.
注意:您可以使用核心圖形函數(shù)
CGContextDrawImage
將圖像繪制到任何渲染目的地辱士。這個(gè)函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)用于圖形上下文听绳,第二個(gè)參數(shù)用于矩形识补,矩形定義了圖像的大小及其在繪圖表面(如視圖)中的位置。當(dāng)你使用CGContextDrawImage
繪制圖像后辫红,如果你不調(diào)整當(dāng)前的坐標(biāo)系統(tǒng)到LLO定位凭涂,圖像在UIKit視圖中的顯示時(shí)倒立的。此外贴妻,該矩形傳遞到該函數(shù)的原點(diǎn)是相對于當(dāng)被調(diào)用函數(shù)中的坐標(biāo)系原點(diǎn)的切油。
使用不同坐標(biāo)繪圖的副作用
當(dāng)參照一種繪圖技術(shù)的默認(rèn)坐標(biāo)系繪制對象并在另一種繪圖技術(shù)的圖形上下文中呈現(xiàn)對象時(shí),一些渲染怪異就會顯現(xiàn)出來名惩。你可能需要調(diào)整你的代碼來考慮這些副作用澎胡。
圓弧與旋轉(zhuǎn)
如果使用諸如CGContextAddArc
和CGPathAddArc
之類的函數(shù)繪制路徑并假定為LLO坐標(biāo)系,則需要翻轉(zhuǎn)CTM以在UIKit視圖中正確呈現(xiàn)弧。但是攻谁,如果使用相同的函數(shù)創(chuàng)建帶有位于ULO坐標(biāo)系中的點(diǎn)的弧稚伍,然后在UIKit視圖中呈現(xiàn)路徑,則會注意到該弧是原始弧的改變版本戚宦「鍪铮弧的終止端點(diǎn)現(xiàn)在指向與該端點(diǎn)將使用UIBezierPath類創(chuàng)建的弧相反的方向。例如受楼,向下指向的箭頭現(xiàn)在指向向上(如圖1-5所示)垦搬,并且弧形“彎曲”的方向也不同⊙奁考慮基于ULO的坐標(biāo)系,您必須更改Core Graphic繪制的弧的方向猴贰;這個(gè)方向由這些函數(shù)的startAngle
和endAngle
參數(shù)控制。
如果你旋轉(zhuǎn)一個(gè)對象(例如河狐,通過調(diào)用CGContextRotateCTM
)米绕,你可以觀察到同樣的鏡像效果。如果使用引用ULO坐標(biāo)系的Core Graphic調(diào)用旋轉(zhuǎn)對象馋艺,則在UIKit中呈現(xiàn)對象的方向是相反的栅干。使用CGContextRotateCTM時(shí),您必須在代碼中考慮不同的旋轉(zhuǎn)方向丈钙;可以通過反轉(zhuǎn)角度參數(shù)的符號(例如非驮,負(fù)值變成正值)來完成此操作交汤。
陰影
陰影從其對象上落下的方向由偏移值指定雏赦,并且該偏移的含義是繪圖框架的一種約定。在UIKit中芙扎,正X和Y偏移使陰影向下并向著物體的右側(cè)星岗。在Core Graphic中,正X和Y偏移使陰影上升到物體的右邊戒洼。翻轉(zhuǎn)CTM以使對象與UIKit的默認(rèn)坐標(biāo)系對齊不會影響對象的陰影俏橘,因此陰影不會正確跟蹤對象。要使其正確跟蹤圈浇,必須對當(dāng)前坐標(biāo)系適當(dāng)?shù)匦薷钠浦怠?/p>
注意:在iOS 3.2之前寥掐,Core Graphics和UIKit共享了陰影方向的相同約定:正偏移值使陰影向下移動(dòng)并指向?qū)ο蟮挠覀?cè)。
應(yīng)用Core Animation來制造效果
Core Animation是一個(gè)Objective-C框架磷蜀,它為快速和方便地創(chuàng)建流暢召耘、實(shí)時(shí)的動(dòng)畫提供了基礎(chǔ)設(shè)施。核心動(dòng)畫本身不是繪圖技術(shù)褐隆,因?yàn)樗惶峁┯糜趧?chuàng)建形狀污它、圖像或其他類型的內(nèi)容的原始功能。相反,它是一種用來操作和顯示內(nèi)容(使用其他繪圖技術(shù)創(chuàng)建的)的技術(shù)衫贬。
大多數(shù)應(yīng)用程序都可以從iOS中的某種形式的核心動(dòng)畫中獲益德澈。動(dòng)畫可以給用戶關(guān)于正在發(fā)生的事情提供一種反饋。例如固惯,當(dāng)用戶在“設(shè)置”應(yīng)用程序中導(dǎo)航時(shí)梆造,屏幕會根據(jù)用戶是沿著首選項(xiàng)層次結(jié)構(gòu)進(jìn)一步導(dǎo)航還是返回到根節(jié)點(diǎn)而滑動(dòng)進(jìn)出視圖。這種反饋是重要的缝呕,并為用戶提供上下文信息澳窑。它還增強(qiáng)了應(yīng)用程序的視覺風(fēng)格。
在大多數(shù)情況下供常,你可以輕松地獲得核心動(dòng)畫的好處摊聋。例如,UIView類的幾個(gè)屬性(包括視圖的frame栈暇、中心麻裁、顏色和不透明度等)可以被配置為在值改變時(shí)觸發(fā)動(dòng)畫。您必須做一些工作來讓UIKit知道您希望執(zhí)行這些動(dòng)畫源祈,但是動(dòng)畫本身是為您創(chuàng)建并自動(dòng)運(yùn)行的煎源。有關(guān)如何觸發(fā)內(nèi)置視圖動(dòng)畫的信息,請參見UIView Class Reference中的動(dòng)畫內(nèi)容香缺。
當(dāng)你想做些基本動(dòng)畫外事情時(shí)手销,你必須更直接地與核心動(dòng)畫類和方法進(jìn)行交互。以下部分提供有關(guān)Core Animation的信息图张,并展示如何使用它的類和方法在iOS中創(chuàng)建典型的動(dòng)畫锋拖。有關(guān)核心動(dòng)畫和如何使用它的更多信息,請參見核心動(dòng)畫編程指南祸轮。
關(guān)于Layer
Core Animation的關(guān)鍵技術(shù)是layer對象兽埃。layer是輕量級對象,本質(zhì)上類似于視圖适袜,但實(shí)際上是模型對象柄错,它封裝了要顯示的內(nèi)容的幾何、定時(shí)和可視屬性苦酱。內(nèi)容本身以三種方式之一提供:
- 可以將CGImageRef分配給layer對象的
contents
屬性 - 可以將delegate分配給圖層售貌,并讓delegate處理繪圖
- 可以對
CALayer
子類進(jìn)行重寫,并重寫其中一種顯示方法疫萤。
當(dāng)您操縱層對象的屬性時(shí)颂跨,您實(shí)際操縱的是模型級數(shù)據(jù),它決定了應(yīng)該如何顯示關(guān)聯(lián)的內(nèi)容给僵。該內(nèi)容的實(shí)際渲染與代碼分開處理毫捣,并進(jìn)行了優(yōu)化详拙,以確保其快速完成。您必須做的是設(shè)置layer內(nèi)容蔓同,配置動(dòng)畫屬性饶辙,然后讓Core Animation接管。
關(guān)于動(dòng)畫
當(dāng)涉及到動(dòng)畫層斑粱,核心動(dòng)畫使用單獨(dú)的動(dòng)畫對象來控制動(dòng)畫的時(shí)間和行為弃揽。CAAnimation
類及其子類提供了可以在代碼中使用的不同類型的動(dòng)畫行為。您可以創(chuàng)建將屬性從一個(gè)值遷移到另一個(gè)值的簡單動(dòng)畫则北,或者您可以創(chuàng)建復(fù)雜的關(guān)鍵幀動(dòng)畫矿微,該關(guān)鍵幀動(dòng)畫通過您提供的一組值和定時(shí)函數(shù)跟蹤動(dòng)畫。
核心動(dòng)畫還可以將多個(gè)動(dòng)畫組合成一個(gè)單一的單元尚揣,稱為事務(wù)涌矢。CATransaction
對象管理一組動(dòng)畫來作為一個(gè)動(dòng)畫單元。你也可以使用這個(gè)類的方法來設(shè)置動(dòng)畫的持續(xù)時(shí)間快骗。
有關(guān)如何創(chuàng)建自定義動(dòng)畫的示例娜庇,參加demoAnimation Types and Timing Programming Guide
考慮縮放因子和Core Animation Layer
直接使用核心動(dòng)畫layer來提供內(nèi)容的應(yīng)用程序可能需要調(diào)整繪圖代碼中的scale因子。通常方篮,當(dāng)您在視圖的drawRect:
方法中或在layer的委托方法drawLayer:inContext:
中繪制時(shí)名秀,系統(tǒng)會自動(dòng)調(diào)整圖形上下文的scale因子。然而藕溅,當(dāng)你的view做下列操作時(shí)匕得,知道或改變這個(gè)scale因子仍然是必要的:
- 創(chuàng)建具有不同scale因子的額外的核心動(dòng)畫layer,并將它們組合成自己的內(nèi)容
- 直接設(shè)置核心動(dòng)畫layer的
contents
屬性
Core Animation的合成引擎查看每個(gè)層的contentsScale
屬性巾表,以確定在合成期間是否需要縮放該層的內(nèi)容汁掠。如果應(yīng)用程序創(chuàng)建沒有關(guān)聯(lián)view的layer,則每個(gè)新layer對象的Scale因子最初被設(shè)置為1.0攒发。如果不改變比例因子调塌,如果隨后在高分辨率屏幕上繪制圖層晋南,則自動(dòng)調(diào)整圖層的內(nèi)容以補(bǔ)償比例因子的差異惠猿。如果不希望縮放內(nèi)容,可以通過為contentsScale
屬性設(shè)置新值將層的縮放因子更改為2.0负间,但是如果這樣做而不提供高分辨率內(nèi)容偶妖,則現(xiàn)有內(nèi)容可能看起來比預(yù)期的小。要解決這個(gè)問題政溃,你需要為你的層提供更高分辨率的內(nèi)容趾访。
重要提示:層的
contentsGravity
屬性在決定是否在高分辨率屏幕上縮放標(biāo)準(zhǔn)分辨率的layer中的內(nèi)容方面發(fā)揮作用。默認(rèn)情況下董虱,此屬性設(shè)置為值kCAGravityResize
扼鞋,這導(dǎo)致層內(nèi)容被縮放以適應(yīng)層的邊界申鱼。將gravity更改為非調(diào)整大小(nonresizing)選項(xiàng)會消除自動(dòng)縮放,否則將發(fā)生縮放云头。在這種情況下捐友,您可能需要相應(yīng)地調(diào)整內(nèi)容或比例因子(scale)。
當(dāng)直接設(shè)置layer的contents
屬性時(shí)溃槐,調(diào)整layer的content以適應(yīng)不同的scale因子是明智選擇匣砖。Quartz圖像沒有scale因子的概念,因此直接與像素一起工作昏滴。因此猴鲫,在創(chuàng)建用為layer內(nèi)容的CGImageRef
對象之前,請檢查scale因子并相應(yīng)地調(diào)整圖像的大小谣殊。特別地拂共,從應(yīng)用程序bundle中加載適當(dāng)大小的圖像,或者使用UIGraphicsBeginImageContextWithOptions
函數(shù)創(chuàng)建圖像時(shí), 其scale因子需要與layer的scale因子匹配的姻几。如果不創(chuàng)建高分辨率位圖匣缘,則現(xiàn)有的bitmap可以按前面討論的那樣縮放。
關(guān)于如何設(shè)置和加載高分辨率圖像請看Loading Images into Your App,