iOS 2D繪圖詳解(Quartz 2D)之概述
前言:最近在研究自訂控制項,由於想要徹底的定製控制項的視圖還是要繼承UIView,雖然對CALayer及其子類很熟練,但是對Quartz 2D這個強大的架構仍然概念模稜兩可。於是,決定學習下,暫訂7篇文章講解,會寫一些Demo。
官方文檔
本文的代碼Demo在最後一部分
Quartz 2D用來幹嘛的?
Quartz 2D屬於Core Graphics(所以大多數相關方法的都是以CG開頭),是iOS/Mac OSX 提供的在核心之上的強大的2D繪圖引擎,並且這個繪圖引擎是裝置無關的。也就是說,不用關心裝置的大小,裝置的解析度,只要利用Quartz 2D,這些裝置相關的會自動處理。Quartz 2D能夠提供的強大功能如下
透明層(transparency layers) 陰影 基於path的繪圖(path-based drawing) 離屏渲染(offscreen rendering) 複雜的顏色處理(advanced color management) 消除鋸齒渲染(anti-aliased rendering) PDF建立,展示,解析(這部分不在這個系列之中) 配合Core Animation, OpenGL ES,UIKit完成複雜的功能畫板-The Graphics Context
既然提到繪圖,那自然有一個容器來包含繪製的結果,然後把這個結果渲染到螢幕上去,而Quartz 2D的容器就是CGContextRef資料模型。這種資料模型是C的結構體,儲存了渲染到螢幕上需要的一切資訊。
那麼,最後 Graphics Context 可以渲染到哪裡呢?
Layer Window 印表機 PDF Bitmap(圖片)
繪製模型
Quartz 2D採用painter’s model,意味著每一次繪製都是一層,然後按照順序一層層的疊加到畫板上。例如
資料類型
Quartz 2D中的資料類型都是透明的,也就是說使用者只需要使用即可,不需要實際訪問其中的變數。具體的資料類型包括
CGPathRef 路徑類型,用來繪製路徑(注意帶有ref尾碼的一般都是繪製的畫板) CGImageRef,繪製bitmap CGLayerRef,繪製layer,layer可複用,可離屏渲染 CGPatternRef,重複繪製 CGShadingRef和CGGradientRef,繪製漸層(例如色彩坡形) CGFunctionRef,定義回呼函數,CGShadingRef和CGGradientRef的輔助類型 CGColorRef and CGColorSpaceRef,定義如何處理顏色 CGFontRef,繪製文字 其他類型(pdf這個系列不講解,所以不會涉及)繪製狀態
在使用quartz 2d進行繪圖的時候,經常需要設定顏色,字型,設定context的座標原點變換,context旋轉。這些影響的都是當前繪製狀態。Context中利用堆棧的方式來儲存繪製狀態。調用CGContextSaveGState來儲存當前繪製狀態的copy到堆棧中,利用CGContextRestoreGState彈出堆棧最頂層的繪製狀態,設定為當前的繪製狀態。注意,不是所有的參數都會儲存,以下表格中的參數會儲存
座標系
和UIKit的座標系不一樣,Quartz 2D的座標系是在左下角的。
Quartz利用座標系的旋轉位移等操作來繪製複雜的動畫。
但是有兩個地方的座標系是正常的UIKit座標系
UIView 的context 通過這個方法UIGraphicsBeginImageContextWithOptions返回的context
一個簡單的Demo講解
建立一個UIView的子類,然後重寫drawRect
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor); CGContextFillRect(context, rect);//先填充整個地區 CGRect testRect = CGRectMake(10, 10, 20, 20); CGContextAddRect(context, testRect); CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);//修改畫筆顏色 CGContextFillRect(context, testRect);//填充部分地區}
然後,這樣調用
LeoDemoView * demoView = [[LeoDemoView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; [self.view addSubview:demoView];
效果
可以看到座標系是正常的UIKit座標系
然後,我們在在上面繪製一個文字”Leo”,在上述drawRect的最後添加,<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHByZSBjbGFzcz0="brush:java;"> NSString * toDraw = @Leo; [toDraw drawAtPoint:CGPointMake(CGRectGetWidth(rect)/2, CGRectGetHeight(rect)/2) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12],NSForegroundColorAttributeName:[UIColor greenColor]}];
可以看到,繪製是一層一層覆蓋的,
最後,我們在右下角繪製一個紅心,但是希望紅心是反過來的。這裡用到了上文的所說的繪製狀態堆棧。繼續在drawRect的最後添加如下代碼
CGContextSaveGState(context); CGContextTranslateCTM(context,rect.size.width,rect.size.height); CGContextRotateCTM(context, M_PI); NSString * redHeart = @??;//MarkDown 顯示不出紅心 [redHeart drawAtPoint:CGPointMake(0, 0) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15]}]; CGContextRestoreGState(context);}
效果
這裡,初學者可能不懂到底是怎麼回事了。我通過圖解的方式一步步解釋
這行代碼把座標系移動到右下角
CGContextTranslateCTM(context,rect.size.width,rect.size.height)
;
接著把座標系逆時針旋轉180度
CGContextRotateCTM(context, M_PI);
這時候的座標系
這時候,參考這個座標系進行繪製,看到的就是反過來的。哇哢哢!