級別: ★★☆☆☆
標籤:「iOS」「drawRect」「繪製」「重繪」
作者: dac_1033
審校: QiShare團隊
一、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方法進行重繪。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,還有
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(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
iOS 編寫高質量Objective-C程式碼(八)
iOS KVC與KVO簡介
iOS 本地化(IB篇)
iOS 本地化(非IB篇)
奇舞週刊