玩轉(zhuǎn)iOS中的繪圖(Quartz 2D基礎(chǔ)篇)

前言: 本篇簡(jiǎn)單介紹使用Quartzs 2D來繪圖.如果你在網(wǎng)上搜索關(guān)于iOS開發(fā)中對(duì)于圖片的處理(縮放...),或者運(yùn)用截取屏幕或者某一部分內(nèi)容來實(shí)現(xiàn)一些動(dòng)畫交互(比如實(shí)現(xiàn)tableViewCell, collectionView的移動(dòng)效果...), 以及自定義一些控件(比如圓形進(jìn)度條...)那么大多數(shù)的處理都會(huì)涉及到繪圖的處理, 也許很多功能在你需要實(shí)現(xiàn)的時(shí)候, 搜索一下可以找到一些解決問題的代碼, 但是或許在使用很久之后還是不明白其中的知識(shí)點(diǎn). 本篇大多內(nèi)容源于對(duì)官方文檔"Quartz 2D Programming Guide"的理解.

首先對(duì)于Quartz 2D就不多做介紹了,(處理路徑的繪圖, 透明度繪圖, 遮蓋,陰影, 顏色管理, 防鋸齒渲染, 生成PDF...) 感興趣的朋友可以查查相關(guān)的資料

1. 了解Quartz 2D的繪圖機(jī)制

  • 和現(xiàn)實(shí)中的繪圖一樣, 首先需要一張"畫布", 在iOS中被稱為 "圖形上下文", 這個(gè)圖形上下文包含了設(shè)備的信息等很多系統(tǒng)準(zhǔn)備好的內(nèi)容
  • 在上下文中運(yùn)用一些方法來設(shè)置我們要繪制的內(nèi)容和屬性,來實(shí)現(xiàn)繪圖
  • Quartz 2D類似打印機(jī), 對(duì)不同的內(nèi)容的繪圖順序不同, 得到的結(jié)果就不相同
    這里有一張官方的圖的解釋,很直接


    painters_model.gif

2.獲取上下文

  • 重寫UIView的drawRect()方法, 當(dāng)這個(gè)view的內(nèi)容需要更新的時(shí)候會(huì)調(diào)用這個(gè)方法, 所以系統(tǒng)在這個(gè)方法里面會(huì)默認(rèn)提供一個(gè)上下文, 可以通過UIGraphicsGetCurrentContext() 獲取到
  • 在其他地方可以通過UIGraphicsBeginImageContextWithOptions()開啟一個(gè)上下文, 然后通過 UIGraphicsGetCurrentContext() 可以獲取到
  • 關(guān)于drawRect()方法(處理不當(dāng)時(shí)有人稱為"內(nèi)存惡魔")
    // 調(diào)用setNeedsDisplay()這個(gè)函數(shù)會(huì)觸發(fā)drawRect方法, 當(dāng)這個(gè)函數(shù)被調(diào)用的時(shí)候, 系統(tǒng)會(huì)通知這個(gè)UIView整個(gè)界面需要重繪. 但是這個(gè)函數(shù)會(huì)直接返回, 但界面的變化不會(huì)馬上返回, 需要等到下個(gè)生命周期進(jìn)行重繪
    // 如果只有一部分需要重繪 可以調(diào)用這個(gè)方法 setNeedsDisplayInRect(rect: CGRect)
    // 這個(gè)方法里面應(yīng)該只做與界面的繪制有關(guān)的工作, 不應(yīng)該進(jìn)行任何的數(shù)據(jù)處理或者程序的邏輯相關(guān)的任務(wù), 以保證盡快執(zhí)行完畢

3. 坐標(biāo)系

  • 和數(shù)學(xué)中的直角坐標(biāo)系相同, 原點(diǎn)在左下角, UIKit中的坐標(biāo)系的原點(diǎn)在左上角, 所以使用Quartz 2D繪圖的時(shí)候要注意進(jìn)行坐標(biāo)系的轉(zhuǎn)換

4. 內(nèi)存管理

  • 因?yàn)槭褂玫腃語言的東西, 編譯器并沒有給我們自動(dòng)管理內(nèi)存, 在oc中需要我們自己來管理, 即當(dāng)調(diào)用含有create或者copy的函數(shù)獲得上下文的時(shí)候, 需要在使用完后手動(dòng)release, 但在swift中我們不用管理了(而且C語言的函數(shù)在swift中看上去也比較習(xí)慣)

5. 繪制我們需要的內(nèi)容

  • 5.1 繪制路徑

    • 首先需要我們畫出路徑 -> 指定起始點(diǎn)到另一個(gè)點(diǎn), 然后兩點(diǎn)之間可以使用直線或者曲線來連接, 就形成了一條路徑, 例如使用下面的函數(shù)來實(shí)現(xiàn)
    ```
    

    CGContextMoveToPoint() -> 指定起始點(diǎn)或者移動(dòng)到新的點(diǎn)
    CGContextAddLineToPoint() -> 從當(dāng)前的點(diǎn)到指定的點(diǎn)之間畫一條線
    CGContextAddArc() -> 圓弧
    CGContextAddArcToPoint() -> 會(huì)在當(dāng)前點(diǎn)和指定點(diǎn)與坐標(biāo)系平行的直線做切線畫圓弧
    CGContextAddCurveToPoint() -> 畫曲線(bezier)
    CGContextClosePath() -> 將起始點(diǎn)和結(jié)束點(diǎn)連接起來形成封閉的路徑
    CGContextAddEllipseInRect() -> 橢圓
    ```

    • 然后設(shè)置需要繪制的路徑的屬性 -> 設(shè)置顏色,透明度,寬度, 繪制模式...例如使用如下的函數(shù)來實(shí)現(xiàn)
    ```
    

CGContextSetLineWidth() -> 線寬度
CGContextSetLineJoin() -> 線的連接點(diǎn)的樣式
CGContextSetLineCap() -> 端點(diǎn)的樣式
CGContextSetLineDash() -> 虛線
CGContextSetStrokeColorWithColor() -> stroke模式時(shí)的顏色
CGContextSetFillColorWithColor() -> fill模式時(shí)的顏色
```
* 繪制內(nèi)容, 注意會(huì)涉及到兩種方式: fill 和 stroke, 這里解釋一下兩者的區(qū)別

  • 當(dāng)使用stroke方式 -> "描邊"即只繪制路徑
  • 當(dāng)使用fill方式"填充"即繪制路徑包括的所有區(qū)域(所有路徑都會(huì)被當(dāng)作closePath處理)

  • Filling有兩種模式
    nonzero winding number(默認(rèn)) -> 如果兩個(gè)路徑有重疊的時(shí)候, 繪制方向相同的話, 那么重疊部分的繪制可能不是我們希望的
    even-odd -> 不受繪制方向的影響
    CGContextEOFillPath
    CGContextFillPath
    CGContextFillRect
    CGContextFillRects
    CGContextFillEllipseInRect

      CGContextStrokePath(context)
      CGContextFillPath(context)
    
    
  • 示例
    stroke模式繪制線

```
override func drawRect(rect: CGRect) {
    // Drawing code
    // 獲取當(dāng)前上下文
    let context = UIGraphicsGetCurrentContext()
    let strokeColor = UIColor.blueColor()
    // 設(shè)置stroke模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor)
    // 設(shè)置線的寬度
    CGContextSetLineWidth(context, 5.0)
    // 設(shè)置連接處樣式...還可以設(shè)置很多其他的屬性
    CGContextSetLineJoin(context, CGLineJoin.Round)
    // 起始點(diǎn)
    CGContextMoveToPoint(context, 10.0, 10.0)
    // 在點(diǎn)(10.0, 10.0)和(10.0, 80.0)之間畫一條線 所以點(diǎn)(10.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
    CGContextAddLineToPoint(context, 10.0, 80.0)
    //在點(diǎn)(10.0, 80.0)和(80.0, 80.0)之間畫一條線 所以點(diǎn)(80.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
    CGContextAddLineToPoint(context, 80.0, 80.0)
    // 設(shè)置為封閉路徑, 會(huì)將首尾點(diǎn)連接起來
    // CGContextClosePath(context)
    // 繪制當(dāng)前的路徑
    CGContextStrokePath(context)
    
}

```
不封閉路徑png
封閉路徑.png

fill模式繪制相同的路徑

     let fillColor = UIColor.blueColor()
     // 設(shè)置fill模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
     CGContextSetFillColorWithColor(context, fillColor.CGColor)
     // 起始點(diǎn)
     CGContextMoveToPoint(context, 10.0, 10.0)
     // 在點(diǎn)(10.0, 10.0)和(10.0, 80.0)之間畫一條線 所以點(diǎn)(10.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
     CGContextAddLineToPoint(context, 10.0, 80.0)
     //在點(diǎn)(10.0, 80.0)和(80.0, 80.0)之間畫一條線 所以點(diǎn)(80.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
     CGContextAddLineToPoint(context, 80.0, 80.0)
     //        CGContextClosePath(context)
     // 繪制當(dāng)前的路徑
     CGContextFillPath(context)
   ```

![fill方式.png](http://upload-images.jianshu.io/upload_images/1271831-3729cb7a3c50b757.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

stroke模式繪制圓

    // 設(shè)置stroke模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor)
    // 設(shè)置線的寬度
    CGContextSetLineWidth(context, 5.0)
    // 設(shè)置連接處樣式
    CGContextSetLineJoin(context, CGLineJoin.Round)
    // 起始點(diǎn)
    let circleCenter = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
    CGContextMoveToPoint(context, circleCenter.x*2, circleCenter.y)
    // 畫圓
    // x -> 圓心的x坐標(biāo)
    // y -> 圓心的y坐標(biāo)
    // radius -> 圓的半徑
    // startAngle -> 起始角度 (弧度制) 所以上面調(diào)整了起始點(diǎn)
    // endAngle -> 結(jié)束角度(弧度制)
    CGContextAddArc(context, circleCenter.x, circleCenter.y, rect.width * 0.5, 0.0, CGFloat(M_PI) * 2, 0)
    CGContextStrokePath(context)

![stroke圓png](http://upload-images.jianshu.io/upload_images/1271831-80e1a3a80711373c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

fill方式繪制圓

    // 設(shè)置fill模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
    CGContextSetFillColorWithColor(context, fillColor.CGColor)
    // 起始點(diǎn)
    let circleCenter = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
    CGContextMoveToPoint(context, circleCenter.x*2, circleCenter.y)
    // 畫圓
    // x -> 圓心的x坐標(biāo)
    // y -> 圓心的y坐標(biāo)
    // radius -> 圓的半徑
    // startAngle -> 起始角度 (弧度制) 所以上面調(diào)整了起始點(diǎn)
    // endAngle -> 結(jié)束角度(弧度制)
    CGContextAddArc(context, circleCenter.x, circleCenter.y, rect.width * 0.5, 0.0, CGFloat(M_PI) * 2, 0)
    // 繪制當(dāng)前的路徑
    CGContextFillPath(context)

![fill方式.png](http://upload-images.jianshu.io/upload_images/1271831-da58c13499f817c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

-----
#####僅僅介紹了上面的幾種看上去很簡(jiǎn)單的繪制方法, 也許你會(huì)覺得沒有什么實(shí)際用處, 但是實(shí)際上是很有用處的, 因?yàn)榈浆F(xiàn)在為止, 你可以使用Quartzs 2D來實(shí)現(xiàn)自定義的各種圓形進(jìn)度條了,(可能使用到stroke和fill的混合模式), 只需要在提供的progress屬性設(shè)置時(shí)調(diào)用setNeedsDisplay()就可以觸發(fā)drawRect()方法進(jìn)行重繪, 而在這里面你就可以利用progress來改變繪制的內(nèi)容了, 從而實(shí)現(xiàn)進(jìn)度條的效果
---
> 限于篇幅和時(shí)間, 這篇中只是梳理了一些基本的繪圖概念和繪制簡(jiǎn)單的圖形(實(shí)際上看看API就可以類似的使用相關(guān)的方法繪制出其他的圖形 -- 橢圓...), 而實(shí)際上還有比較多的東西需要研究, 以后有必要在補(bǔ)上. [Demo](https://github.com/jasnig/DrawingStudy)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發(fā)出絢麗的界面效果,一方面得益于成功系統(tǒng)的設(shè)計(jì),另一方面得益...
    韓七夏閱讀 2,981評(píng)論 2 10
  • Quartz 2D是一個(gè)二維圖形繪制引擎,支持iOS環(huán)境和Mac OS X環(huán)境。我們可以使用Quartz 2D A...
    Eiwodetianna閱讀 2,126評(píng)論 1 251
  • 坐車回家路上路過麻城站,剛好遇到今年冬天最大的一場(chǎng)雪,一路上大學(xué)紛飛,白雪皚皚。對(duì)面有輛停下的列車,車上的乘客好像...
    林文意閱讀 507評(píng)論 0 1
  • 最近總是能在各種網(wǎng)站上看見許多濃濃的“雞湯”,上面針對(duì)思考方面我覺得千篇一律,無非都說凡事不要想太多,想太多注定就...
    瓷貓閱讀 379評(píng)論 0 0
  • 我到目前為止覺得微積分不難,但為什么我的微積分考試的成績(jī)總是不高呢?考試的時(shí)候總是出錯(cuò)呢?
    鄧茜Daisy閱讀 119評(píng)論 0 0

友情鏈接更多精彩內(nèi)容