今天給同學(xué)們?nèi)娴闹v解Quartz2D以及Quartz2D的相關(guān)實戰(zhàn)例子:例子請參考我個人CSDN之前所發(fā)的有關(guān)Quartz2D的項目的博文,那么廢話不多說,直接上代碼~
http://blog.csdn.net/ZZ_IOSdeveloper
- Quartz2D簡介
- Quartz2D能完成的工作
- Quartz2D在iOS開發(fā)中的價值
- Quartz2D必須掌握(
重點) - 圖形上下文&圖形上下文棧&常用函數(shù)
- Quartz2D繪圖演練(畫線\圖形\進度條\餅塊\柱狀圖......)
- 手勢解鎖
- 矩陣操作
- Quartz2D的內(nèi)存管理
Quartz2D簡介
- 什么是Quartz2D?二維的繪圖引擎
- 什么是二維?平面
- 什么是引擎?經(jīng)包裝的函數(shù)庫,方便開發(fā)者使用。也就是說蘋果幫我們封裝了一套繪圖的函數(shù)庫
- 同時支持iOS和Mac系統(tǒng)什么意思?用Quartz2D寫的同一份代碼,既可以運行在iphone上又可以運行在mac上,可以跨平臺開發(fā)。
- 開發(fā)中比較常用的是截屏/裁剪/自定義UI控件。
- Quartz2D在iOS開發(fā)中的價值就是自定義UI控件。
- 圖形上下文的數(shù)據(jù)類型和作用。
- 有多少種上下文。
- 自定義控件的步驟。
- 為什么要實現(xiàn)drawRect:方法,因為只有在drawRect:方法中才能獲取到上下文
Quartz2D能完成的工作
- 繪制圖形 : 線條\三角形\矩形\圓\弧等
- 繪制文字
- 繪制\生成圖片(圖像)
- 讀取\生成PDF
- 截圖\裁剪圖片
- 自定義UI控件
- ......
Quartz2D在iOS開發(fā)中的價值
- 為了便于搭建美觀的UI界面,iOS提供了UIKit框架,里面有各種各樣的UI控件
UILabel:顯示文字
UIImageView:顯示圖片
UIButton:同時顯示圖片和文字(能點擊)
… … - 利用UIKit框架提供的控件,拼拼湊湊,能搭建和現(xiàn)實一些簡單、常見的UI界面
- 但是,有些UI界面極其復(fù)雜、而且比較個性化,用普通的UI控件無法實現(xiàn),這時可以利用Quartz2D技術(shù)將控件內(nèi)部的結(jié)構(gòu)畫出來,自定義控件的樣子
- 其實,iOS中大部分控件的內(nèi)容都是通過Quartz2D畫出來的
- 因此,Quartz2D在iOS開發(fā)中很重要的一個價值是:自定義view(
自定義UI控件)
Quartz2D必須掌握
- drawRect:方法的使用
- 常見圖形的繪制:線條、多邊形、圓
- 繪圖狀態(tài)的設(shè)置:文字顏色、線寬等
- 圖形上下文狀態(tài)的保存與恢復(fù)(圖形上下文棧)
- 圖片裁剪
- 截圖
drawRect:中取得的上下文
- 在drawRect:方法中取得上下文后,就可以繪制東西到view上
- View內(nèi)部有個layer(圖層)屬性,drawRect:方法中取得的是一個Layer Graphics Context,因此,繪制的東西其實是繪制到view的layer上去了
- View之所以能顯示東西,完全是因為它內(nèi)部的layer
圖形上下文
- 圖形上下文(Graphics Context):是一個CGContextRef類型的數(shù)據(jù)
- 圖形上下文的作用:
-
保存繪圖信息、繪圖狀態(tài)決定繪制的輸出目標(biāo)(繪制到什么地方去?)(輸出目標(biāo)可以是PDF文件、Bitmap或者顯示器的窗口上)
繪圖原理 - 相同的一套繪圖序列,指定不同的Graphics Context,就可將相同的圖像繪制到不同的目標(biāo)上
- Quartz2D提供了以下幾種類型的Graphics Context:
- Bitmap Graphics Context
- PDF Graphics Context
- Window Graphics Context
- Layer Graphics Context
-
Printer Graphics Context
圖形上下文
常用拼接路徑函數(shù)
新建一個起點
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)
添加新的線段到某個點
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)
添加一個矩形
void CGContextAddRect(CGContextRef c, CGRect rect)
添加一個橢圓
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)
添加一個圓弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,
CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
常用繪制路徑函數(shù)
Mode參數(shù)決定繪制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
繪制空心路徑
void CGContextStrokePath(CGContextRef c)
繪制實心路徑
void CGContextFillPath(CGContextRef c)
提示:一般以CGContextDraw、CGContextStroke、CGContextFill開頭的函數(shù),都是用來繪制路徑的
圖形上下文棧
將當(dāng)前的上下文copy一份,保存到棧頂(那個棧叫做”圖形上下文?!?
void CGContextSaveGState(CGContextRef c)
將棧頂?shù)纳舷挛某鰲?替換掉當(dāng)前的上下文
void CGContextRestoreGState(CGContextRef c)
Quartz2D繪圖演練
- 在哪畫? storyboard拖一個view,在這個view里面畫一些東西。
- 自定義view:需要繪圖,就必須重寫drawRect:方法
- ZZLineView:繪制線段
- drawRect:方法自動生成,意味著什么?這個方法很重要。
- 什么時候調(diào)用:視圖要顯示的時候,才會調(diào)用,viewDidLoad后才會調(diào)用,因為那時候還沒顯示視圖。
- 作用:用來繪圖
畫線
1> 獲取圖形上下文
CG:表示這個類在CoreGraphics框架里 Ref:引用目前學(xué)的上下文都跟UIGraphics有關(guān),想獲取圖形上下文,首先敲UIGraphics。
2> 拼接路徑:一般開發(fā)中用貝塞爾路徑,里面封裝了很多東西,可以幫我畫一些基本的線段,矩形,圓等等。
* 創(chuàng)建貝塞爾路徑
* 起點:moveToPoint
* 終點:addLineToPoint
3> 把路徑添加到上下文
CGPath轉(zhuǎn)換:UIKit框架轉(zhuǎn)CoreGraphics直接CGPath就能轉(zhuǎn)
4> 把上下文渲染到視圖,圖形上下文本身不具備顯示功能。
* 首先獲取圖形上下文,然后描述路徑,把路徑添加到上下文,渲染到視圖,圖形上下文相當(dāng)于一個內(nèi)存緩存區(qū),在內(nèi)存里面操作是最快的,比直接在界面操作快多了。
5> 在添加一根線
直接addLineToPoint,因為路徑是拼接的,默認下一條線的起點是上一條線的終點。
6> 畫兩跟不連接的線
* 第二次畫的時候,重新設(shè)置起點,然后畫線。一個路徑可以包含多條線段。
* 新創(chuàng)建一個路徑,添加到上下文。開發(fā)中建議使用這種,比較容易控制每根線。
7> 設(shè)置繪圖狀態(tài)
* 線段怎么加粗。
* 繪圖狀態(tài)調(diào)用順序:只要在渲染之前就好了,在渲染的時候才會去看繪圖的最終狀態(tài)。
8> 畫曲線
* 分析:3個點,起點,終點,控制點。
繪制圖形
triangle三角形
1> 關(guān)閉路徑closePath:從路徑的終點連接到起點
2> 填充路徑CGContextFillPath:有了封閉的路徑就能填充。
3> 設(shè)置填充顏色 [[UIColor blueColor] setFill];
4> 設(shè)置描邊顏色 [[UIColor redColor] setStroke];
5> 不顯示描邊顏色,為什么?沒有設(shè)置線寬
6> 設(shè)置線寬,還是不顯示,為什么?因為繪制路徑不對。
7> 即填充又描邊CGContextDrawPath:kCGPathFillStroke。
rectangle矩形
circle圓:為什么傳入矩形,因為圓內(nèi)切與矩形
arc圓弧
1> 圓弧屬于圓的一部分,因此先要有圓,才有弧。
2> 圓需要起點嗎?畫線需要,圓也不另外。
3> 起點在哪? 圓心右邊
4> 畫圓弧還需要起始角度,結(jié)束角度,方向,角度必須是弧度
Quarter 1/4圓
畫個1/4圓弧,Stroke
填充路徑,F(xiàn)ill
必須有封閉路徑才能填充,沒有封閉路徑,系統(tǒng)會自動關(guān)閉路徑,再去填充
畫線連接圓心,自動關(guān)閉路徑。
基本圖形繪制
繪制線段(Line):繪制直線->在添加一條直線->繪制兩條直線(路徑填充、設(shè)置路徑屬性,顏色,線寬等)->曲線->分析
繪制圖形(Shape):繪制三角形(超人內(nèi)褲)->矩形->圓->圓弧->1/4圓
重繪-下載進度條(setNeedsDisplay)
* 分析有幾個控件:UISliderView,自定義view用來畫圖,UILabel
* 分析思路:滑動UISliderView的時候,把UISliderView的值傳給自定義view,改變的顯示。
* 怎么監(jiān)聽UISliderView,valueChange事件
* 自定義view搞一個屬性接收滑動的值
* 接下來先搞label,因為他比較簡單,做東西,先從簡單的著手。
* 懶加載label,位置居中,文字居中
* label怎么顯示?重寫progress的set方法,有數(shù)據(jù)就展示在label上。
* 繪制圓弧,從哪開始畫圓弧。
* 每次轉(zhuǎn)多少°? 角度 = 進度 * M_PI * 2
* startAngle = -M_PI_2
* endAngle = -M_PI_2 + 進度 * M_PI * 2
* 不會及時更新視圖顯示?為什么,因為drawRect只會在視圖顯示的時候調(diào)用一次。
* 怎么重繪?
* 手動調(diào)用drawRect方法,不行,因為你自己不能創(chuàng)建上下文,必須系統(tǒng)調(diào)用來調(diào)用
* setNeedsDisplay:在view上做一個重繪的標(biāo)記,在下一次繪圖的周期來臨,就會先創(chuàng)建好上下文,然后自動調(diào)用drawRect重繪。
繪制餅圖
* 分析 -> 找規(guī)律 -> 得出startA,endA,angle的結(jié)論
* startA = endA angle = 比例 * 總度數(shù)360° endA = startA + angle
* angle = 自己 / 100.0 * 360
* 一個一個扇形畫
繪制柱狀圖
* 分析 -> 找規(guī)律 -> 得出w,h,y,x的結(jié)論
* w = viewW / (2 * count - 1)
* h = viewH * 比例
UIKit封裝繪圖方法演練(不需要上下文)
- 繪制文本
- 繪制矩形
- 繪制圖像
- 模仿UIImageView/雪花(定時器)
- 圖片水印
- 圖片水印作用:防止他人盜取圖片,加一些Logo,生成一張新的圖片。
- 怎么生成新的圖片?和繪圖一樣的,需要拿到上下文做事情,這里也需要拿到上下文,生成一個新的圖片。
- 什么上下文?位圖上下文,在這個上下文畫東西,就能輸出到新的圖片上。
- 怎么獲???之前用的都是圖層上下文,系統(tǒng)會自動創(chuàng)建,但是我們位圖上下文,需要我們手動創(chuàng)建
- 總結(jié):只要不和view有關(guān)系的上下文,都需要我們手動創(chuàng)建。
- 在哪獲取圖像上下文,viewDidLoad, 不需要拿到系統(tǒng)創(chuàng)建的圖層上下文,沒必要在drawRect方法里寫,直接viewDidLoad就行了。
- 怎么創(chuàng)建圖像上下文了?之前說過跟上下文有關(guān)的以什么開頭,UIGraphics
- UIGraphicsBeginImageContextWithOptions:看注釋,create bitmap,創(chuàng)建一個位圖上下文,而且這種方法得到的圖片最清晰。
- 解釋參數(shù)(size:新圖片尺寸 opaque: YES:不透明 NO:透明 scale:0.0 不伸縮)
- 繪制內(nèi)容(圖片,文字)
- 獲取圖片:把位圖上下文的內(nèi)容生成一個圖片給你。
- 關(guān)閉上下文,不關(guān)閉一直占用著內(nèi)存。
- 顯示UIImageView上
- 保存圖片,寫到文件,UIImage不能寫,需要轉(zhuǎn)換成NSData二進制數(shù)據(jù)
- UIImageJPEGRepresentation:可以設(shè)置圖片質(zhì)量
- UIImagePNGRepresentation:把圖片轉(zhuǎn)換成png格式的二進制數(shù)據(jù),png格式默認是最高清的。
- 寫到桌面
有時候,在手機客戶端app中也需要用到水印技術(shù)
比如,用戶拍完照片后,可以在照片上打個水印,標(biāo)識這個圖片是屬于哪個用戶的
實現(xiàn)方式:利用Quartz2D,將水?。ㄎ淖?、LOGO)畫到圖片的右下角
核心代碼
開啟一個基于位圖的圖形上下文
void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
從上下文中取得圖片(UIImage)
UIImage * UIGraphicsGetImageFromCurrentImageContext();
結(jié)束基于位圖的圖形上下文
void UIGraphicsEndImageContext();
- 圖像裁切 -> 指定裁切區(qū)域之后,所有繪圖信息都只顯示裁切區(qū)域內(nèi)
- 圖片裁剪
- 先設(shè)置裁剪區(qū)域,把圖片畫上去,超出裁剪區(qū)域的自動裁剪掉。
- 加載舊圖片,根據(jù)舊圖片,獲取上下文尺寸。
- 上下文的尺寸 = 新圖片的尺寸
- 開啟一個多大的上下文?:和圖片尺寸一樣大,避免壓縮圖片。如果比圖片尺寸小,會壓縮圖片。
- 設(shè)置裁剪區(qū)域:正切于圖片的圓
- 繪制舊圖片
- 獲取新圖片
- 關(guān)閉上下文
- 帶圓環(huán)裁剪:在裁剪的圖片外邊加個小圓環(huán)。
- 先畫一個大圓,在設(shè)置裁剪區(qū)域,把圖片畫上去,超出裁剪區(qū)域的自動裁剪掉。
- 加載舊圖片,根據(jù)舊圖片,獲取上下文尺寸。
- 確定圓環(huán)寬度 borderW
- 上下文的尺寸 = 新圖片的尺寸
- 確定新的上下文尺寸: newImageW : oldImageW + 2 * borderW newImageH : oldImageH + 2 * borderW,
- 繪制大圓:
- 1.獲取上下文
- 2.添加路徑到上下文
- 3.設(shè)置大圓的顏色 = 圓環(huán)的顏色
- 4.渲染
- 設(shè)置裁剪區(qū)域,和圖片尺寸一樣大,只不過,x,y不一樣,x=borderW,y=borderW.
- 繪制舊圖片
- 獲取新圖片
- 關(guān)閉上下文
- 抽分類,3個參數(shù),圖片名稱,圓環(huán)寬度,圓環(huán)顏色
核心代碼
void CGContextClip(CGContextRef c)
將當(dāng)前上下所繪制的路徑裁剪出來(超出這個裁剪區(qū)域的都不能顯示)
- 屏幕截圖:把屏幕的內(nèi)容截屏生成一張新的圖片
- 通常開發(fā)中,都是把控制器的內(nèi)容截屏,生成新的圖片
- 控制器怎么顯示?根據(jù)view
- view怎么顯示? 根據(jù)layer圖層
- 把layer渲染到位圖上下文
- 注意:圖層只能用渲染,圖片和文字可以用draw
- 渲染在哪?新的圖片
- 開啟圖片上下文,和視圖一樣的尺寸
- 寫入桌面
- 抽分類
核心代碼
- (void)renderInContext:(CGContextRef)ctx;
調(diào)用某個view的layer的renderInContext:方法即可
矩陣操作
利用矩陣操作,能讓繪制到上下文中的所有路徑一起發(fā)生變化
縮放
void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
旋轉(zhuǎn)
void CGContextRotateCTM(CGContextRef c, CGFloat angle)
平移
void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
手勢解鎖
- 分析界面有幾個控件:背景:UIImageView 白色圓圈:按鈕(點擊他,會出現(xiàn)另外一種圖片,按鈕可以設(shè)置不同狀態(tài)下的圖片。)單獨視圖:(畫線是有范圍的,當(dāng)超出view就不能畫線了)
- ZZLoadView:自定義視圖,在視圖一創(chuàng)建的時候,就添加9個按鈕。
- 在initWithCoder,initWithFrame方法添加按鈕。
- 九宮格布局:
tolcols = 3
計算row,col 按鈕的x,y跟col,row有關(guān)系,col = i % tolcol row = i / tolcol
計算邊距 margin = (view.bounds.size.width - tolcol * btnW) / (tolcol + 1)
btnX = margin + (btnW + margin) * col
btnY = (btnW + margin) * row
- 圓的選中
- 點擊按鈕就為選中的圖片怎么做?監(jiān)聽按鈕點擊。
- 不能addTarget: 不能及時顯示選中圖片。
- 監(jiān)聽touchBegin,判斷點在不在按鈕的frame上。
- touchBegin不調(diào)用?原因:事件交給按鈕處理,應(yīng)該把事件交給解鎖視圖。讓按鈕不接收事件。
- 設(shè)置按鈕不允許交互,2個用處:1.不接收事件 2.取消高亮效果 一舉兩得
- 遍歷所有按鈕,看觸摸點在哪個按鈕上,就選中誰,CGRectContainsPoint 傳入的參數(shù)必須是同一個坐標(biāo)系
- 實現(xiàn)touchMove方法:因為手指移動的時候,也需要判斷點在不在按鈕上。
- 抽方法,因為touchMove的方法里,也需要做同樣的事情。
- 1> pointWithTouches 根據(jù)touches集合取出觸摸點
- 2> buttonWithPoint 根據(jù)觸摸點,獲取觸摸按鈕
- 圓的連線
- 哪些需要連線?被選中按鈕之間都需要連線,還有一個多余的線
- 先把選中的按鈕全部連線?為什么,因為多余的那根線是從最后一個按鈕的圓心開始畫,手指移動在哪就畫哪。
- 搞個數(shù)組保存下所有選中按鈕,在drawRect方法中遍歷所有按鈕,連線
- UIBezierPath畫線,不需要上下文。
- 需要多少個UIBezierPath對象?一個,路徑都是連續(xù)的,不相連的,才需要創(chuàng)建新的UIBezierPath。
- 遍歷數(shù)組,描述路徑
- 1> 起點:第一個按鈕的圓心
- 2> 添加一根線到其他按鈕的圓心
- 設(shè)置路徑的顏色和線寬
- 什么時候渲染?把所有路徑都描述完就,渲染一次就夠了。
- [path stroke] 就能渲染到視圖上了。
- 畫多余的那根線,記住手指移動的位置。
- setNeedDisplay 沒有畫出來線?因為drawRect只會調(diào)用一次,需要每次手指移動的時候,都需要重繪。
- touchBegin不調(diào)用setNeedDisplay?那時候重繪也沒用,只有起點。
- lineJoinStyle:有尖尖的東西,線段連接樣式的問題,設(shè)置為平的。
- 已經(jīng)選中的按鈕,不需要再次選中,和畫線
- 手指抬起,取消所有選中按鈕,并且清空數(shù)組,清空線條
- drawRect判斷下沒有選中數(shù)組,不需要畫線。
- 如何判斷用戶是否輸入正確?給選中按鈕綁定tag,遍歷所有選中按鈕,把tag拼接成一個字符串。
Quartz2D的內(nèi)存管理
使用含有“Create”或“Copy”的函數(shù)創(chuàng)建的對象,使用完后必須釋放,否則將導(dǎo)致內(nèi)存泄露
使用不含有“Create”或“Copy”的函數(shù)獲取的對象,則不需要釋放
如果retain了一個對象,不再使用時,需要將其release掉
可以使用Quartz 2D的函數(shù)來指定retain和release一個對象。例如,如果創(chuàng)建了一個CGColorSpace對象,則使用函數(shù)CGColorSpaceRetain和CGColorSpaceRelease來retain和release對象。
也可以使用Core Foundation的CFRetain和CFRelease。注意不能傳遞NULL值給這些函數(shù)