iOS 重繪之drawRect
drawRect是 UIView
類的一個方法,在drawRect中所呼叫的重繪功能是基於 Quartz 2D
實現的, Quartz 2D
是一個二維圖形繪製引擎,支援 iOS
環境和 Mac OS X
環境。利用 UIKit
框架提供的控制元件,我們能實現一些簡單的UI介面,但是,有些UI介面比較複雜,用普通的UI控制元件無法實現,或者實現效果不佳,這時可以利用 Quartz 2D
技術將控制元件內部的結構畫出來,自定義所需控制元件,這也是 Quartz 2D
框架在 iOS
開發中一個很重要的價值。
iOS的繪圖操作是在UIView類的drawRect方法中進行的,我們可以 重寫 一個view的drawRect方法,在其中進行繪圖操作,在首次顯示該view時程式會自動呼叫此方法進行繪圖。 在多次手動重複繪製的情況下,需要呼叫UIView中的 setNeedsDisplay
方法,則程式會自動呼叫drawRect方法進行重繪。 ofollow,noindex">PS:蘋果官網關於drawRect的介紹
二、drawRect的使用過程
在view的 drawRect
方法中,利用 Quartz 2D
提供的API繪製圖形的步驟:
1)新建一個 view
,繼承自UIView,並 重寫 drawRect
方法;
2)在 drawRect
方法中,獲取圖形上下文;
3)繪圖操作;
4)渲染。
三、何為CGContext?
Quartz 2D
是 CoreGraphics
框架的一部分,因此其中的相關類及方法都是以CG為字首。在drawRect重繪過程中最常用的就是 CGContext
類。 CGContext
又叫圖形上下文,相當於一塊畫板,以堆疊形式存放,只有在當前 context
上繪圖才有效。iOS又分多種圖形上下文,其中UIView自帶提供的在drawRect方法中通過 UIGraphicsGetCurrentContext
獲取,還有專門為圖片處理的context,還有 pdf
的context等等均有特定的獲取方法,本文只對第一種做相關介紹。
CGContext
類中的常用方法:
// 獲取當前上下文 CGContextRef context = UIGraphicsGetCurrentContext(); // 移動畫筆 CGContextMoveToPoint // 在畫筆位置與point之間新增將要繪製線段 (在draw時才是真正繪製出來) CGContextAddLineToPoint // 繪製橢圓 CGContextAddEllipseInRect CGContextFillEllipseInRect // 設定線條末端形狀 CGContextSetLineCap // 畫虛線 CGContextSetLineDash // 畫矩形 CGContextAddRect CGContextStrokeRect CGContextStrokeRectWithWidth // 畫一些線段 CGContextStrokeLineSegments // 畫弧: 以(x1, y1)為圓心radius半徑,startAngle和endAngle為弧度 CGContextAddArc(context, x1, y1, radius, startAngle, endAngle, clockwise); // 先畫兩條線從point 到 (x1, y1) , 從(x1, y1) 到(x2, y2) 的線切裡面的圓 CGContextAddArcToPoint(context, x1, y1,x2,y2, radius); // 設定陰影 CGContextSetShadowWithColor // 設定填充顏色 CGContextSetRGBFillColor // 設定畫筆顏色 CGContextSetRGBStrokeColor // 設定填充顏色空間 CGContextSetFillColorSpace // 設定畫筆顏色空間 CGConextSetStrokeColorSpace // 以當前顏色填充rect CGContextFillRect // 設定透明度 CGContextSetAlaha // 設定線的寬度 CGContextSetLineWidth // 畫多個矩形 CGContextAddRects // 畫曲線 CGContextAddQuadCurveToPoint // 開始繪製圖片 CGContextStrokePath // 設定繪製模式 CGContextDrawPath // 封閉當前線路 CGContextClosePath // 反轉畫布 CGContextTranslateCTM(context, 0, rect.size.height);CGContextScaleCTM(context, 1.0, -1.0); // 從原圖片中取小圖 CGImageCreateWithImageInRect // 畫圖片 CGImageRef image=CGImageRetain(img.CGImage); CGContextDrawImage(context, CGRectMake(10.0, height - 100.0, 90.0, 90.0), image); // 實現漸變顏色填充 CGContextDrawLinearGradient(context, gradient, CGPointMake(0.0, 0.0) ,CGPointMake(0.0, self.frame.size.height), kCGGradientDrawsBeforeStartLocation); 複製程式碼
四、用drawRect方法重繪的例項
我們在drawRect方法中繪製一些圖形,如圖:

程式碼實現如下:
- (void)drawRect:(CGRect)rect { //1. 注:如果沒有獲取context時,是什麼都不做的(背景無變化) [super drawRect:rect]; // 獲取上下文 CGContextRef context =UIGraphicsGetCurrentContext(); CGSize size = rect.size; CGFloat offset = 20; // 畫腦袋 CGContextSetRGBStrokeColor(context,1,1,1,1.0); CGContextSetLineWidth(context, 1.0); CGContextAddArc(context, size.width / 2, offset + 30, 30, 0, 2*M_PI, 0); CGContextDrawPath(context, kCGPathStroke); // 畫眼睛和嘴巴 CGContextMoveToPoint(context, size.width / 2 - 23, 40); CGContextAddArcToPoint(context, size.width / 2 - 15, 26, size.width / 2 - 7, 40, 10); CGContextStrokePath(context); CGContextMoveToPoint(context, size.width / 2 + 7, 40); CGContextAddArcToPoint(context, size.width / 2 + 15, 26, size.width / 2 + 23, 40, 10); CGContextStrokePath(context);//繪畫路徑 CGContextMoveToPoint(context, size.width / 2 - 8, 65); CGContextAddArcToPoint(context, size.width / 2, 80, size.width / 2 + 8, 65, 10); CGContextStrokePath(context);//繪畫路徑 // 畫鼻子 CGPoint nosePoints[3]; nosePoints[0] = CGPointMake(size.width / 2, 48); nosePoints[1] = CGPointMake(size.width / 2 - 3, 58); nosePoints[2] = CGPointMake(size.width / 2 + 3, 58); CGContextAddLines(context, nosePoints, 3); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); // 畫脖子 CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); CGContextStrokeRect(context, CGRectMake(size.width / 2 - 5, 80, 10, 10)); CGContextFillRect(context,CGRectMake(size.width / 2 - 5, 80, 10, 10)); //// 畫衣裳 //CGPoint clothesPoints[4]; //clothesPoints[0] = CGPointMake(size.width / 2 - 30, 90); //clothesPoints[1] = CGPointMake(size.width / 2 + 30, 90); //clothesPoints[2] = CGPointMake(size.width / 2 + 100, 200); //clothesPoints[3] = CGPointMake(size.width / 2 - 100, 200); //CGContextAddLines(context, clothesPoints, 4); //CGContextClosePath(context); //CGContextDrawPath(context, kCGPathFillStroke); // 衣裳顏色漸變 CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, size.width / 2 - 30, 90); CGPathAddLineToPoint(path, NULL, size.width / 2 + 30, 90); CGPathAddLineToPoint(path, NULL, size.width / 2 + 100, 200); CGPathAddLineToPoint(path, NULL, size.width / 2 - 100, 200); CGPathCloseSubpath(path); [self drawLinearGradient:context path:path startColor:[UIColor cyanColor].CGColor endColor:[UIColor yellowColor].CGColor]; CGPathRelease(path); // 畫胳膊 CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0 green:1 blue:1 alpha:1].CGColor); CGContextMoveToPoint(context, size.width / 2 - 28, 90); CGContextAddArc(context, size.width / 2 - 28, 90, 80,- M_PI, -1.05 * M_PI, 1); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFill); CGContextMoveToPoint(context, size.width / 2 + 28, 90); CGContextAddArc(context, size.width / 2 + 28, 90, 80,0, 0.05 * M_PI, 0); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFill); // 畫左手 CGPoint aPoints[2]; aPoints[0] =CGPointMake(size.width / 2 - 30 - 81, 90); aPoints[1] =CGPointMake(size.width / 2 - 30 - 86, 90); CGContextAddLines(context, aPoints, 2); aPoints[0] =CGPointMake(size.width / 2 - 30 - 80, 93); aPoints[1] =CGPointMake(size.width / 2 - 30 - 85, 93); CGContextAddLines(context, aPoints, 2); CGContextDrawPath(context, kCGPathStroke); // 畫右手 aPoints[0] =CGPointMake(size.width / 2 + 30 + 81, 90); aPoints[1] =CGPointMake(size.width / 2 + 30 + 86, 90); CGContextAddLines(context, aPoints, 2); aPoints[0] =CGPointMake(size.width / 2 + 30 + 80, 93); aPoints[1] =CGPointMake(size.width / 2 + 30 + 85, 93); CGContextAddLines(context, aPoints, 2); CGContextDrawPath(context, kCGPathStroke); //// 畫虛線 //aPoints[0] =CGPointMake(size.width / 2 + 30 + 81, 90); //aPoints[1] =CGPointMake(size.width / 2 + 30 + 86, 90); //CGContextAddLines(context, aPoints, 2); //aPoints[0] =CGPointMake(size.width / 2 + 30 + 80, 93); //aPoints[1] =CGPointMake(size.width / 2 + 30 + 85, 93); //CGContextAddLines(context, aPoints, 2); //CGFloat arr[] = {1, 1}; //CGContextSetLineDash(context, 0, arr, 2); //CGContextDrawPath(context, kCGPathStroke); // 畫雙腳 CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor); CGContextAddEllipseInRect(context, CGRectMake(size.width / 2 - 30, 210, 20, 15)); CGContextDrawPath(context, kCGPathFillStroke); CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor); CGContextAddEllipseInRect(context, CGRectMake(size.width / 2 + 10, 210, 20, 15)); CGContextDrawPath(context, kCGPathFillStroke); // 繪製圖片 UIImage *image = [UIImage imageNamed:@"img_watch"]; [image drawInRect:CGRectMake(60, 270, 100, 120)]; //[image drawAtPoint:CGPointMake(100, 340)]; //CGContextDrawImage(context, CGRectMake(100, 340, 20, 20), image.CGImage); // 繪製文字 UIFont *font = [UIFont boldSystemFontOfSize:20.0]; NSDictionary *attriDict = @{NSFontAttributeName:font, NSForegroundColorAttributeName:[UIColor redColor]}; [@"繪製文字" drawInRect:CGRectMake(180, 270, 150, 30) withAttributes:attriDict]; } - (void)drawLinearGradient:(CGContextRef)context path:(CGPathRef)path startColor:(CGColorRef)startColor endColor:(CGColorRef)endColor { CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGFloat locations[] = { 0.0, 1.0 }; NSArray *colors = @[(__bridge id) startColor, (__bridge id) endColor]; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations); CGRect pathRect = CGPathGetBoundingBox(path); //具體方向可根據需求修改 CGPoint startPoint = CGPointMake(CGRectGetMidX(pathRect), CGRectGetMinY(pathRect)); CGPoint endPoint = CGPointMake(CGRectGetMidX(pathRect), CGRectGetMaxY(pathRect)); CGContextSaveGState(context); CGContextAddPath(context, path); CGContextClip(context); CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); CGContextRestoreGState(context); CGGradientRelease(gradient); CGColorSpaceRelease(colorSpace); } 複製程式碼
注:
1)當view未設定背景顏色時,重繪區域的背景顏色預設為‘黑’;
2)設定畫筆顏色的方法 CGContextSetRGBStrokeColor
,設定填充顏色的方法 CGContextSetFillColorWithColor
;
3)每次繪製獨立的圖形結束時,都要實時呼叫 CGContextDrawPath
方法來將這個獨立的圖形繪製出來,否則多次 CGContextMoveToPoint
會使繪製的圖形亂掉;
4)區別 CGContextAddArc
與 CGContextAddArcToPoint
;
5)畫虛線時,之後所有的線條均變成虛線(除非再手動設定成是實現)
五、CAShapeLayer繪圖與drawRect重繪的比較
在網上查了一些 CAShapeLayer
與 drawRect
重繪的一些比較,整理如下,有助於我們學習與區分:
(1)兩種自定義控制元件樣式的方法各有優缺點, CAShapeLayer
配合貝賽爾曲線使用時,繪圖形狀更靈活,而 drawRect
只是一個方法而已,在其中更適合繪製大量有規律的通用的圖形;
(2) CALayer
的屬性變化預設會有動畫, drawRect
繪圖沒有動畫;
(3) CALayer
繪製圖形是實時的, drawRect
多次重繪需要手動呼叫 setNeedsLayout
;
(4)效能方面, CAShapeLayer
使用了硬體加速,繪製同一圖形會比用 Core Graphics
快很多, CAShapeLayer
屬於 CoreAnimation
框架,動畫渲染直接提交給手機 GPU
,不消耗內,而 Core Graphics
會消耗大量的 CPU
資源。
另外,原始碼中還通過重繪實現了兩個簡單的排序演算法, 工程原始碼GitHub地址
關注我們的途徑有:
QiShare(微信公眾號)