iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

时间:2023-03-08 18:46:02
iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第一步:先科普一下基础知识:

Core Graphics是基于C的API,可以用于一切绘图操作

Core Graphics 和Quartz 2D的区别

quartz是一个通用的术语,用于描述在IOS和MAC OS X ZHONG 整个媒体层用到的多种技术 包括图形、动画、音频、适配。

Quart 2D 是一组二位绘图和渲染API,Core Graphic会使用到这组API

Quartz Core 专指Core Animation用到的动画相关的库、API和类

点和像素的对比

系统拥有坐标系,如320*480 硬件有retain屏幕和非retain屏:如320*480、640*960

Core Graphics 使用的是系统的坐标系来绘制图片。在分辨率为640*960手机上绘制图片时,实际上Core Graphics 的坐标是320*480。这个时候每个坐标系上的点,实际上拥有两个像素。

图形上下文

Core Graphics 使用图形上下文进行工作,这个上下文的作用像画家的画布一样。

在图形上下文之外是无法绘图的,我们可以自己创建一个上下文,但是性能和内存的使用上,效率是非常低得。

我们可以通过派生一个UIView的子类,获得它的上下文。在UIView中调用drawRect:方法时,会自动准备好一个图形上下文,可以通过调用

UIGraphicsGetCurrentContext()来获取。 因为它是运行期间绘制图片,我们可以动态的做一些额外的操作

Core Graphics的优点

快速、高效,减小应用的文件大小。同时可以*地使用动态的、高质量的图形图像。 使用Core Graphics,可以创建直线、路径、渐变、文字与图像等内容,并可以做变形处理。

绘制自定义视图

drawRect:是系统的方法,不要从代码里面直接调用 drawRect:,而应该使用setNeedsDisplay重绘.

需要知道的术语

  • 路径 path
  • 阴影 shadow
  • 笔画 stroke
  • 剪裁路径 Clip Path
  • 线条粗细 Line Width
  • 混合模式 Blend Mode
  • 填充色 Fill Color
  • 当前形变矩阵 Current Transform Matrix
  • 线条图案 Line Dash

图形上下文

一个图形上下文好比是画布上的一副扁平的图画 执行绘画动作,这些动作是在同一个图层上完成的。 图形上下文不允许将内容分不到多个图层中,如果有需求在不同图层上画,可以考虑使用视图层次结构,创建多个UIView,并将他们作为父视图的子视图

图形上下文栈可以把图形上下文的当前状态保存下来,并在执行一些动作后再次恢复回来

CGContextSaveGState();

CGContextStoreGState();

路径、渐变、文字和图像

1. 使用UIBezierPath创建路径

2. 手动创建路径 moveToPoint addLineToPoint addArcWithCenter addCurveToPoint

渐变,渐变可以在指定方向上,以可变的比率在一系列颜色之间转化

线性渐变:沿着一条定义好了起点和重点的直线方向,呈线性变化。如果这条线有一定角度,线性渐变也会沿相同路径变化

放射渐变:颜色顺着两个原型之间的方向线性变化,这两个园为起始圆和终止圆,每隔圆都有自己的圆心和班级

文字

darwAtPoint

drawInRect

图像

Core Graphics 不会保持图像的长宽比例,Core Graphics会将图像的边界设置为CGrect,不管图片是否变形 darwAtPoint drawInRect

第二步:代码部分:

基础画法就不多讲啦!都通用:

第一种绘图形式:在UIView的子类方法drawRect:中绘制一个蓝色圆,使用UIKit在Cocoa为我们提供的当前上下文中完成绘图任务。
 
    - (void) drawRect: (CGRect) rect { 

    UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 

    [[UIColor blueColor] setFill]; 

    [p fill]; 

    } 

第二种绘图形式:使用Core Graphics实现绘制蓝色圆。
 
    - (void) drawRect: (CGRect) rect { 

    CGContextRef con = UIGraphicsGetCurrentContext(); 

    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 

    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 

    CGContextFillPath(con); 

    } 

第三种绘图形式:我将在UIView子类的drawLayer:inContext:方法中实现绘图任务。drawLayer:inContext:方法是一个绘制图层内容的代理方法。为了能够调用drawLayer:inContext:方法,我们需要设定图层的代理对象。但要注意,不应该将UIView对象设置为显示层的委托对象,这是因为UIView对象已经是隐式层的代理对象,再将它设置为另一个层的委托对象就会出问题。轻量级的做法是:编写负责绘图形的代理类。在MyView.h文件中声明如下代码:
 
    @interface MyLayerDelegate : NSObject 

    @end 

然后MyView.m文件中实现接口代码:
 
    @implementation MyLayerDelegate 

    - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { 

      UIGraphicsPushContext(ctx); 

      UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 

      [[UIColor blueColor] setFill]; 

      [p fill]; 

      UIGraphicsPopContext(); 

    } 

    @end 

直接将代理类的实现代码放在MyView.m文件的#import代码的下面,这样感觉好像在使用私有类完成绘图任务(虽然这不是私有类)。需要注意的是,我们所引用的上下文并不是当前上下文,所以为了能够使用UIKit,我们需要将引用的上下文转变成当前上下文。
因为图层的代理是assign内存管理策略,那么这里就不能以局部变量的形式创建MyLayerDelegate实例对象赋值给图层代理。这里选择在MyView.m中增加一个实例变量,因为实例变量默认是strong:
 
    @interface MyView () { 

    MyLayerDelegate* _layerDeleagete; 

    } 

    @end 

使用该图层代理:
 
    MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)]; 

    CALayer *myLayer = [CALayer layer]; 

    _layerDelegate = [[MyLayerDelegate alloc] init]; 

    myLayer.delegate = _layerDelegate; 

    [myView.layer addSublayer:myLayer]; 

    [myView setNeedsDisplay]; // 调用此方法,drawLayer: inContext:方法才会被调用。 

第四种绘图形式: 使用Core Graphics在drawLayer:inContext:方法中实现同样操作,代码如下:
 
    - (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con { 

    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 

    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 

    CGContextFillPath(con); 

    } 

最后,演示UIGraphicsBeginImageContextWithOptions的用法,并从上下文中生成一个UIImage对象。生成UIImage对象的代码并不需要等待某些方法被调用后或在UIView的子类中才能去做。
第五种绘图形式: 使用UIKit实现:
 
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 

    UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 

    [[UIColor blueColor] setFill]; 

    [p fill]; 

    UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 

    UIGraphicsEndImageContext(); 

解释一下UIGraphicsBeginImageContextWithOptions函数参数的含义:第一个参数表示所要创建的图片的尺寸;第二个参数用来指定所生成图片的背景是否为不透明,如上我们使用YES而不是NO,则我们得到的图片背景将会是黑色,显然这不是我想要的;第三个参数指定生成图片的缩放因子,这个缩放因子与UIImage的scale属性所指的含义是一致的。传入0则表示让图片的缩放因子根据屏幕的分辨率而变化,所以我们得到的图片不管是在单分辨率还是视网膜屏上看起来都会很好。
第六种绘图形式: 使用Core Graphics实现:
 
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 

    CGContextRef con = UIGraphicsGetCurrentContext(); 

    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 

    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 

    CGContextFillPath(con); 

    UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 

    UIGraphicsEndImageContext();

第三步:实践部分:

第一种:基本图形绘制

/**
 *  什么调用:当你视图第一次显示的时候就会调用
 *  作用:绘图
 *  @param rect = self.bounds
 */
- (void)drawRect:(CGRect)rect
{
    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.拼接路径
    UIBezierPath *path = [UIBezierPath bezierPath];

    CGPoint startP = CGPointMake(10, 125);
    CGPoint endP = CGPointMake(240, 125);
    CGPoint controlP = CGPointMake(125, 0);
    [path moveToPoint:startP];
    [path addQuadCurveToPoint:endP controlPoint:controlP];

    // 3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);

    // 4.渲染上下文到视图
    CGContextStrokePath(ctx);
}

- (void)drawLine
{
    // 1.获取上下文
    // CGContextRef CG CoreGraphics Ref 引用
    // 目前学的上下文都跟UIGraphics有关,以后想直接获取上下文,直接敲一个UIGraphics
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.设置绘图信息(拼接路径)
    UIBezierPath *path = [UIBezierPath bezierPath];

    // 设置起点
    [path moveToPoint:CGPointMake(10, 10)];

    // 添加一条线到某个点
    [path addLineToPoint:CGPointMake(125, 125)];
    [path addLineToPoint:CGPointMake(240, 10)];
    // 3.把路径添加到上下文
    // 直接把UIKit的路径转换成CoreGraphics,CG开头就能转
    CGContextAddPath(ctx, path.CGPath);

    // 4.把上下文渲染到视图
    // Stroke描边
    CGContextStrokePath(ctx);
}

- (void)draw2Line
{
    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.拼接路径
    UIBezierPath *path = [UIBezierPath bezierPath];

    // 设置起点
    [path moveToPoint:CGPointMake(10, 125)];

    // 添加一条线到某个点
    [path addLineToPoint:CGPointMake(230, 125)];

    //    // 设置起点
    //    [path moveToPoint:CGPointMake(10, 10)];
    //
    //    // 添加一条线到某个点
    //    [path addLineToPoint:CGPointMake(125, 100)];

    UIBezierPath *path1 = [UIBezierPath bezierPath];

    [path1 moveToPoint:CGPointMake(10, 10)];

    [path1 addLineToPoint:CGPointMake(125, 100)];

    // 3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);
    CGContextAddPath(ctx, path1.CGPath);

    // 设置绘图状态
    // 设置线宽
    CGContextSetLineWidth(ctx, 10);
    CGContextSetLineCap(ctx, kCGLineCapRound);
    //    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
    [[UIColor redColor] set];

    // 4.渲染上下文到视图
    CGContextStrokePath(ctx);

}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第二种:下载进度条:

- (void)setProgress:(CGFloat)progress
{
    _progress = progress;
    self.myLabel.text = [NSString stringWithFormat:@"%.2f%%",progress*100];
    //    [self drawRect:self.bounds];
    // 重新绘制
    // 在view上做一个重绘的标记,当下次屏幕刷新的时候,就会调用drawRect.
    [self setNeedsDisplay];
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.

 // 当视图显示的时候会调用 默认只会调用一次
- (void)drawRect:(CGRect)rect
{
     // 1.获取上下文
     CGContextRef ctx = UIGraphicsGetCurrentContext();

     // 2.拼接路径
     CGPoint center = CGPointMake(50, 50);
     CGFloat radius = 50 - 2;
     CGFloat startA = -M_PI_2;
     CGFloat endA = -M_PI_2 + _progress * M_PI * 2;
     UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];

     // 3.把路径添加到上下文
     CGContextAddPath(ctx, path.CGPath);

     // 4.把上下文渲染到视图
     CGContextStrokePath(ctx);

}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第三种:饼图

- (void)drawRect:(CGRect)rect
{
    // Drawing code

    NSArray *data = @[@25,@25,@50];

    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.拼接路径
    CGPoint center = CGPointMake(125, 125);
    CGFloat radius = 120;
    CGFloat startA = 0;
    CGFloat angle = 0;
    CGFloat endA = 0;

    for (NSNumber *number in data) {
        // 2.拼接路径
        startA = endA;
        angle = number.intValue / 100.0 * M_PI * 2;
        endA = startA + angle;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
        [path addLineToPoint:center];

        [[UIColor randomColor] set];
        // 把路径添加上下文
        CGContextAddPath(ctx, path.CGPath);

        // 渲染
        CGContextFillPath(ctx);

    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGFloat a = arc4random_uniform(6);
    //CGFloat a =  arc4random()%6;
    NSLog(@"随机数--%f",a);

    [self setNeedsDisplay];
}

- (void)drawPie
{
    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.拼接路径
    CGPoint center = CGPointMake(125, 125);
    CGFloat radius = 120;
    CGFloat startA = 0;
    CGFloat angle = 0;
    CGFloat endA = 0;

    // 第一个扇形
    angle = 25 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path addLineToPoint:center];
    // 添加到上下文
    CGContextAddPath(ctx, path.CGPath);
    [[UIColor redColor] set];

    // 渲染
    CGContextFillPath(ctx);

    // 第二个扇形
    startA = endA;
    angle = 25 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path1 addLineToPoint:center];
    // 添加到上下文
    CGContextAddPath(ctx, path1.CGPath);
    [[UIColor greenColor] set];
    // 渲染
    CGContextFillPath(ctx);

    // 第三个扇形
    startA = endA;
    angle = 50 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path2 addLineToPoint:center];
    // 添加到上下文
    CGContextAddPath(ctx, path2.CGPath);
    [[UIColor blueColor] set];
    // 渲染
    CGContextFillPath(ctx);

}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第四种:柱形图

- (void)drawRect:(CGRect)rect
{
     NSArray *data = @[@25,@25,@50];
     NSInteger count = data.count;

     CGFloat w = rect.size.width / (2 * count - 1);
     CGFloat h = 0;
     CGFloat x = 0;
     CGFloat y = 0;
     CGFloat viewH = rect.size.height;
     // 1.获取上下文
     CGContextRef ctx = UIGraphicsGetCurrentContext();

     for (int i = 0; i < count; i++) {
     h = viewH * [data[i] intValue] / 100.0;
     x = 2 * w * i;
     y = viewH - h;
     // 2.拼接路径
     UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];

     // 3.添加路径到上下文
     CGContextAddPath(ctx, path.CGPath);

     [[UIColor randomColor] set];

     // 4.渲染
     CGContextFillPath(ctx);
     }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
     [self setNeedsDisplay];
}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第五种:模仿UIImageView

- (void)setImage:(UIImage *)image
 {
     _image = image;
     [self setNeedsDisplay];
}

 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
 // Drawing code

     [_image drawInRect:rect];
}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客



每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第六种:图形上下文线

- (void)drawRect:(CGRect)rect
{
    // Drawing code

    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 把ctx拷贝一份放在栈中
    CGContextSaveGState(ctx);

    // 2.拼接路径(绘图的信息)
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(10, 125)];
    [path addLineToPoint:CGPointMake(240, 125)];

    // 3.路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);

    // 设置绘图的状态
    [[UIColor redColor] set];
    CGContextSetLineWidth(ctx, 10);
    CGContextSetLineCap(ctx, kCGLineCapRound);

    // 4.渲染
    CGContextStrokePath(ctx);

    // 第二根线
    UIBezierPath *path1 = [UIBezierPath bezierPath];
    [path1 moveToPoint:CGPointMake(125, 10)];
    [path1 addLineToPoint:CGPointMake(125, 240)];
    CGContextAddPath(ctx, path1.CGPath);

    // 把栈顶上下文取出来,替换当前上下文
    CGContextRestoreGState(ctx);

    // 设置绘图的状态
    //    [[UIColor blackColor] set];
    //    CGContextSetLineWidth(ctx, 1);
    //    CGContextSetLineCap(ctx, kCGLineCapButt);

    // 4.渲染
    CGContextStrokePath(ctx);

}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

第七种:矩形操作

- (void)drawRect:(CGRect)rect
{
    // Drawing code

    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 注意:你的路径一定放在上下文矩阵操作之后
    // 平移上下文
    CGContextTranslateCTM(ctx, 50, 100);

    // 旋转上下文
    CGContextRotateCTM(ctx, M_PI_4);

    // 缩放上下文
    CGContextScaleCTM(ctx, 0.5, 1.2);

    // 2.拼接路径
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-50, -100, 150, 200)];

    // 3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);

    [[UIColor redColor] set];

    // 4.渲染
    CGContextFillPath(ctx);

}

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

iOS开发者交流QQ群: 446310206  欢迎加入(demo在这里)!