iOS 重绘之drawRect

1. 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方法进行重绘。苹果官网关于drawRect的介绍

2. drawRect的使用过程

在view的drawRect方法中,利用Quartz 2D 提供的API绘制图形的步骤:
1)新建一个view,继承自UIView,并重写drawRect方法;
2)在drawRect方法中,获取图形上下文;
3)绘图操作;
4)渲染。

3. 何为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);

5. 用drawRect方法重绘的实例

我们在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)画虚线时,之后所有的线条均变成虚线(除非再手动设置成是实现)

6. 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地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容