什么是繪圖引擎
如果您以前從事其它平台的圖形/界面開發或者游戲開發,一定知道, 不管上層UI怎么呈現和響應, 底層必須有一個繪圖引擎. iOS也不例外. 本文詳細介紹了iOS Graphics的用法和相關知識, 希望對您的Coding有幫助.
- 博客: http://www.cnblogs.com/jhzhu
- 郵箱: jhzhuustc@gmail.com
- 作者: 知明所以
- 時間: 2013-12-31
^此博客需要對CALayer
和UIView
有基本的了解. 可參考博客談談iOS Animation
什么是繪圖引擎
繪圖引擎, 通俗來說就好比給你一張紙一支筆和若干顏色的顏料, 你可以用它來做最基本的圖形繪制.
一個最基本的繪圖引擎包括一下接口:
//Code-1
I1. drawLine() //繪制任意線條,並支持對線條上色.
I2. drawPath() //根據路徑繪制形狀,並支持填充顏色.
I3. drawImage() //繪制圖像(e.g. xxx.jpg, xxx.png)
I4. drawGradient() //繪制漸變填充
I5. transform() //矩陣映射變換.
I6. drawText() //繪制文字
不難想象, 有了以上接口, 我們就可以方便的繪制任意想要的圖像.
這里強調的是方便, 有些接口並不是必須的. 比如說drawImage()
,我們總可以調用有限次drawLine()
和drawShape()
來繪制任意給定的Image. 但是復雜程度可想而知.
一個繪圖引擎設計的目的就是為了方便上層調用, 所以它會封裝一些最常用和最基本的接口. 以上5個接口就滿足這兩個條件之一. 所謂最常用和最基本並沒有一個明確的定義, 所以不同的繪圖引擎可能會多一些常用接口,但都大同小異.
iOS的繪圖引擎
下面我們就Code-1
里提到的接口在iOS平台上做一個介紹.
在哪里繪制?
如果我們在XCode里新建一個UIView
類, 我們會得到以下代碼:
//Code-2
#import "GraphicsViewControllerView.h"
@implementation GraphicsViewControllerView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code }
return self;
}
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
@end
通常,drawRect()
都會被注釋起來. 因為, 如果你向UIView
添加subView
或者設置UIView
的顯示相關的屬性(e.g. backgroundCrolor
)的時候, UIKit
會自動的把這些參數代表的含義繪制到CALayer
上去. 也就是說, 一般情況我們並不需要自己來繪制, UIKit
會自動幫我們完成繪制工作.
但是, 當不添加subView
, 不設置UIView
的顯示相關的屬性時, 我們就可以通過重載drawRect()
來手動繪制圖像了.
Context
A graphical context can be thought of as a canvas, offering an enormous number of properties such as pen color, pen thickness, etc. Given the context, you can start painting straight away inside the drawRect: method, and Cocoa Touch will make sure that the attributes and properties of the context are applied to your drawings. We will talk about this more later, but now, let’s move on to more interesting subjects.
drawText
我們新建一個UIView
類CustomUIView
,如下重載drawRect()
方法.
新建CustomUIView
的對象,不設置任何屬性,添加到顯示列表.
//Code-3
- (void)drawRect:(CGRect)rect{
UIFont *font = [UIFont systemFontWithSize:40.f];
NSString *myString = @"Some String";
[myString drawAtPoint:CGPointMake(40, 180) withFont:font];
}
運行, 就可以看到我們沒有添加任何UITextField
卻顯示了文字~
more:
關於文字繪制的方法還有 drawInRect:withFont:
等幾個方法, 可參考官方文檔.
setColor
我們把Code-3
中的代碼添加兩行, 變成:
//Code-4
- (void)drawRect:(CGRect)rect{
UIColor* color = [UIColor blueColor]; //create color
[color set]; //set color
UIFont *font = [UIFont systemFontWithSize:40.f];
NSString *myString = @"Some String";
[myString drawAtPoint:CGPointMake(40, 180) withFont:font];
}
就可以看到,文字由黑色變成了藍色.
more:
UIColor
還有兩個方法setStroke
和setFill
分別設置線條顏色和填充顏色. 在后面的章節會用到.set
方法影響所有前景色.
drawImage
同樣的, 如下重寫drawRect
方法:
//Code-5
- (void)drawRect:(CGRect)rect{
/* Assuming the image is in your app bundle and we can load it */
UIImage *xcodeIcon = [UIImage imageNamed:@"filename.png"];
[xcodeIcon drawAtPoint:CGPointMake(0.0f, 20.0f)];
}
我們看到圖像被繪制出來了.
more:
UIImage
還有其他繪制方法:
drawInRect:
drawAsPatternInRect:
drawAtPoint:blendMode:alpha:
drawInRect:blendMode:alpha:
方法名已經很清楚的說明了方法的用途. 具體可參考官方文檔
drawLine
這兩個是繪圖引擎里最基本的, 所以放在一起講述.
//Code-6: drawLine
- (void)drawRect:(CGRect)rect{
/* Step1 設置繪圖顏色 */
[[UIColor brownColor] set];
/* Step2 獲取當期的畫布: Graphic Context */
CGContextRef currentContext = UIGraphicsGetCurrentContext();
/* Step3 設置線條寬度 */
CGContextSetLineWidth(currentContext,5.0f);
/* Step4 把畫筆移動到起始點 */
CGContextMoveToPoint(currentContext,50.0f, 10.0f);
/* Step5 從起始點繪制線條到終點 */
CGContextAddLineToPoint(currentContext,100.0f, 200.0f);
/* Step6 提交繪制 */
CGContextStrokePath(currentContext);
}
如果想連續繪制多條線, 可以再Code-6
中的Step5
和Step6
之間多次調用CGContextAddLineToPoint()
.
more:
CGContextSetLineJoin
可以改變線條交叉點的樣式.
drawPath
如果我們想快速繪制一條折線, 調用drawLine
就顯得有些臃腫. 所以有了drawPath
, 它是drawLine
的加強版.
drawPath
的一般步驟如下:
//Code-7: drawPath
- (void)drawRect:(CGRect)rect{
/* Step1 獲取當期的畫布: Graphic Context */
CGContextRef currentContext = UIGraphicsGetCurrentContext();
/* Step2 創建 path */
CGMutablePathRef path = CGPathCreateMutable();
/* Step3 移動到起始點 */
CGPathMoveToPoint(path,NULL, screenBounds.size.width, screenBounds.origin.y);
/* Step4 繪制一個橢圓 */
CGPathAddEllipseInRect(path, &CGAffineTransformIdentity, CGRectMake(0, 320, 320, 160));
/* Step5 再添加一條直線 */
CGPathAddLineToPoint(path,NULL, screenBounds.origin.x, screenBounds.size.height);
/* Step6 向畫布添加path */
CGContextAddPath(currentcontext, path);
/* Step7 設置繪制類型: kCGPathStroke(繪制邊緣), kCGPathFill(填充path內區域), kCGPathFillStroke(包含前面兩項)*/
CGContextDrawPath(currentcontext, kCGPathFillStroke);
/* Step8 提交繪制 */
CGContextStrokePath(currentContext);
/* Step9 release path */
CGPathRelease(path);
}
more:
常見幾何圖形的繪制接口: CGPathAddCurveToPoint
,CGPathAddArcToPoint
,CGPathAddRect
等等...
transform
iOS中的transform
使用CGAffineTransform
表示的. 你可以用矩陣方式構造任意二維變換:
/*
a: The value at position [1,1] in the matrix.
b: The value at position [1,2] in the matrix.
c: The value at position [2,1] in the matrix.
d: The value at position [2,2] in the matrix.
tx: The value at position [3,1] in the matrix.
ty: The value at position [3,2] in the matrix.
*/
CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty)
矩陣表示為:
iOS也提供了常見變換的快速構建方式:
CGAffineTransformIdentity //單位矩陣, 不做任何變換
CGAffineTransformMakeRotation //旋轉
CGAffineTransformMakeScale //縮放
CGAffineTransformMakeTranslation //平移
在Code-7 Step4
中, 繪制path
時用到了單位矩陣CGAffineTransformIdentity
, 表示不做任何變換. 通常情況下, 都是在繪制階段把transform
作為參數傳入. 上面提到的CGPathAddCurveToPoint
,CGPathAddArcToPoint
,CGPathAddRect
函數都有一個transform
參數.
iOS繪圖在項目中的應用
通常, 我們只需要隨心所欲的對UIView
增加subView
, UIKit
會自動幫我們繪制. 但是下列情況下可能需要手動繪制:
- 優化
UITableViewCell
的時候. 如果我們的cell
很復雜, 有很多subView
, 就會變得很卡頓. 就需要手動繪制了. 可參考博客: 優化UITableView性能或者IOS詳解TableView——性能優化及手工繪制UITableViewCell - 暫未想到, 以后想到再說.