天天看点

quartz2D 的从零到一学习使用(附赠源码)

转载自:http://www.jianshu.com/p/f9f05e12ddd3#

quartz2D 的从零到一学习使用(附赠源码)

demo中有苹果婊

什么是Quartz2D?二维的绘图引擎

什么是二维?平面

什么是引擎?经包装的函数库,方便开发者使用。也就是说苹果帮我们封装了一套绘图的函数库

同时支持iOS和Mac系统什么意思?用Quartz2D写的同一份代码,既可以运行在iphone上又可以运行在mac上,可以跨平台开发。

开发中比较常用的是截屏/裁剪/自定义UI控件。

Quartz2D在iOS开发中的价值就是自定义UI控件。

使用图形上下文画图,要遵循一下四个步骤

1.获取图像上下文

2.创建路径

3.将路径添加到图形上下文(add)

4.渲染图像上下文(fill,stroke)

一.绘制一条直线的方法

1.直接使用图形上下文画图

//只有在drawRect方法中才能拿到图形上下文,才可以画图
- (void)drawRect:(CGRect)rect {
   //1.获取图形上下文,目前我们现在使用的都是UIGraphics开头,CoreGraphics,项目的简称CG
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //2.描述路径
    //2.1 创建路径
    CGContextMoveToPoint(ctx, , );
    //2.2 添加线到一个点
    CGContextAddLineToPoint(ctx, , );

  //3.完成路线
    CGContextStrokePath(ctx);
}
           

2.使用图形上下文+CGPathRef画线

- (void)drawRect:(CGRect)rect {
    //1.获取图形上下文,目前我们现在使用的都是UIGraphics开头,CoreGraphics,项目的简称CG
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.使用path来画线
    CGMutablePathRef path = CGPathCreateMutable();
    //3.添加点
    CGPathMoveToPoint(path, NULL, , );
    CGPathAddLineToPoint(path, NULL, , );

    //4.将path添加到图像上下文上
    CGContextAddPath(ctx, path);
    //5.渲染上下文
    CGContextStrokePath(ctx);
}
           

3.贝塞尔曲线

实际上方法都是一样的,只是添加没有获取图形上下文,在底层,系统已经给封装了
- (void)drawRect:(CGRect)rect {
    //1.创建路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    //2.画线
    [path moveToPoint:CGPointMake(, )];
    [path addLineToPoint:CGPointMake(, )];
    //3.渲染
    [path stroke];
}
           

4.图形上下文+贝塞尔曲线

- (void)drawRect:(CGRect)rect {
   //使用贝塞尔曲线和图形上下文画图
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointZero];
    [path addLineToPoint:CGPointMake(, )];
   //这个是C语言的写法,必须使用path.CGPath,否则无效
    CGContextAddPath(ctx, path.CGPath);
    CGContextStrokePath(ctx);
}
           

1.用四种方法,说明绘制图形有很多种方法

2.底层的是

2.CGMutablePathRef

,绘制图形实际上就是一直设置

path

3.

CGContextMoveToPoint(实际上底层已经给我们分装了CGPat,但只有一个)

c语言的方法,

UIBezierPath

抽象出的面向对象的类

4.不管哪种,思路都是一样的

5.个人推荐的是使用1,2,3,不推荐4,两个东西杂糅,不太喜欢

二.画两个链接的线,并且设置属性

quartz2D 的从零到一学习使用(附赠源码)

两条相交的线

//只有在drawRect方法中才能拿到图形上下文,才可以画图
- (void)drawRect:(CGRect)rect {
    //1.获取图形上文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.将绘制路径,并且将其添加到图形上下文
    CGContextMoveToPoint(ctx,, );
    CGContextAddLineToPoint(ctx, , );

    //添加另一根线
    CGContextAddLineToPoint(ctx, , );

    //设置颜色
    [[UIColor greenColor] set];
    //设置线的宽度
    CGContextSetLineWidth(ctx, );
    //设置链接处的链接类型
    CGContextSetLineJoin(ctx, kCGLineJoinMiter);
    //设置线的头部样式
    CGContextSetLineCap(ctx, kCGLineCapButt);

    //渲染
    CGContextStrokePath(ctx);

}
           
画出了两条有链接的线,其中设置颜色的时候,是区分设置线的颜色,和设置图片的颜色的,可以设置他们各自的属性(但是经常设置错误),懒得去区分并且保证不会设置错误,建议设置

[[UIColor greenColor] set]

就不用考虑实线还是填充图形了。还有,

CGContextSetLineJoin

是设置连接处的样式,是枚举,

CGContextSetLineCap

是设置线的顶部的样式,也是枚举。

注意:设置各种属性的时候,一定要记住在渲染之前,否则无效

三.绘制两条不相交的线,并且设置各自属性

quartz2D 的从零到一学习使用(附赠源码)

两条不相交的线,设置他们的属性

- (void)drawRect:(CGRect)rect {
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(, )];
    [path addLineToPoint:CGPointMake(, )];
    [path setLineWidth:];
    [[UIColor redColor] set];
    [path stroke];
    //设置第2个线段
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointMake(, )];
    [path2 addLineToPoint:CGPointMake(, )];
    [path2 setLineWidth:];
    [[UIColor greenColor] set];
    [path2 setLineCapStyle:kCGLineCapRound];
    [path2 stroke];
}
           
使用贝塞尔曲线画图的好处在于,1.每一个贝塞尔底层都有一个图形上线文,如果是用

CGContextMoveToPoint

画图,实际上就是一个图形上下文,不好去控制,所以建议没多条线可以使用贝塞尔曲线或者说使用底层的

CGMutablePathRef

画线,比较靠谱

四.绘制曲线

quartz2D 的从零到一学习使用(附赠源码)

曲线

- (void)drawRect:(CGRect)rect {
    //绘制一条曲线
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //设置起点
    CGContextMoveToPoint(ctx, , );
    /**
     *  添加曲线的五个参数
     *
     *  @param c#>   图形上下文
     *  @param cpx#> 将来要突出的x值
     *  @param cpy#> 要突出的y值
     *  @param x#>   曲线结束时的x
     *  @param y#>   曲线结束时的y
     */
    CGContextAddQuadCurveToPoint(ctx, , , , );

    //设置颜色
    [[UIColor redColor] set];

    //渲染图层
    CGContextStrokePath(ctx);
}
           

五.绘制带有圆角边框的正方形

quartz2D 的从零到一学习使用(附赠源码)

圆角的矩形们

- (void)drawRect:(CGRect)rect {
    //绘制一个圆角矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(, , , ) cornerRadius:];
    [[UIColor orangeColor] set];
    [path stroke];

    //绘制一个圆角矩形
    UIBezierPath *path2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(, , , ) cornerRadius:];
    [[UIColor greenColor] set];
    [path2 fill];

    //绘制一个原形(不规范的绘制)
    UIBezierPath *path3 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(, , , ) cornerRadius:];
    [[UIColor cyanColor] set];
    [path3 fill];
}                
  • 1.

    stroke

    设置边框的颜色,

    fill

    填充内部的颜色
  • 2.

    fill

    并不是随意使用的,必须是封闭的图形。
  • 3.可以通过设置圆角是正方形的高度,生成一个原形,但不是最规范绘制原形的方法,不过也可以使用。

六.绘制一个弧度曲线

quartz2D 的从零到一学习使用(附赠源码)

弧度曲线

- (void)drawRect:(CGRect)rect {
    /**
     *  绘制弧度曲线
     *
     *  @param ArcCenter 曲线中心
     *  @param radius       半径
     *  @param startAngle 开始的弧度
     *  @param endAngle 接收的弧度
     *  @param clockwise YES顺时针,NO逆时针
     */
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(, ) radius: startAngle: endAngle:M_PI clockwise:YES];
    [[UIColor greenColor] set];
    [path stroke];

    //结束角度如果是M_PI *2 就是一个圆
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(, ) radius: startAngle: endAngle:M_PI *  clockwise:YES];
    [[UIColor purpleColor] set];
    [path1 fill];

    //绘制115度角的圆弧
    UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(, ) radius: startAngle: endAngle:/*(M_PI *) clockwise:NO];
    [[UIColor orangeColor] set];
    [path2 stroke];

}                
1.

M_PI

是180度.

M_PI_2

是90°

2.这里的角度都是弧度制度,如果我们需要15°,可以用

15°/180°*π

得到。

3.

clockwise

这个是顺时针,如果穿1,就是

顺时针

,穿0,是

逆时针

七.绘制一个一个扇形

quartz2D 的从零到一学习使用(附赠源码)

扇形

- (void)drawRect:(CGRect)rect {
    //1.获取图形上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //绘制曲线
    CGFloat centerX = ;
    CGFloat centerY = ;
    CGFloat radius = ;
    //添加一根线·
    CGContextMoveToPoint(ctx, centerX, centerY);
    CGContextAddArc(ctx, centerX, centerY, radius, M_PI, M_PI_2, NO);
    //关闭线段
    CGContextClosePath(ctx);
    //渲染
    CGContextStrokePath(ctx);
}
           
1.线添加一个点

CGContextMoveToPoint

2.添加一个圆弧

CGContextAddArc

3.闭合绘图

CGContextClosePath

4.给路径设置颜色

CGContextStrokePath

,或者给图形内部设置颜色

CGContextFillPath

5.使用贝塞尔曲线,也要设置闭合路径

CGContextClosePath

八.简单下载进度的demo

quartz2D 的从零到一学习使用(附赠源码)

demo下载进度

- (void)setProgressValue:(CGFloat)progressValue{
    _progressValue = progressValue;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {

    CGFloat radius = self.frame.size.height *  - ;
    CGFloat endA = self.progressValue*M_PI* - M_PI_2;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius+,radius+) radius:radius- startAngle:-M_PI_2 endAngle:endA clockwise:YES];
    path.lineWidth = ;
    path.lineCapStyle = kCGLineCapRound;
    [[UIColor redColor] set];
    [path stroke];

}
           
  1. demo上传github,DEMO链接,上边是重要的代码

    2.在

    UIBezierPath

    bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise:

    中clockwise: == 1表示顺时针

    3.

    CGContextAddArc(ctx, centerX, centerY, radius, M_PI, M_PI_2, NO)

    ,clockwise == 0表示顺时针

    4.想调用这个方法

    drawRect:(CGRect)rect

    ,必须要使用

    setNeedsDisplay

    ,其他的无效。

九.饼状图

quartz2D 的从零到一学习使用(附赠源码)

饼状图

@interface WXView ()
@property(nonatomic,strong)NSArray *nums;
@property(assign,nonatomic)NSInteger total;
@end
@implementation WXView


-(NSInteger )total{
    if (_total == ) {
        for (int j = ; j < self.nums.count; j++) {
            _total += [self.nums[j] integerValue];
        }
    }
    return _total;
}

-(NSArray *)nums{
    if (!_nums) {
        _nums = @[@,@,@,@,@,@,@];
    }
    return _nums;
}

//只有在drawRect方法中才能拿到图形上下文,才可以画图
- (void)drawRect:(CGRect)rect {

    //绘制一个饼状图
    CGFloat centX = ;
    CGFloat centY = ;
    CGPoint center = CGPointMake(centX, centY);
    CGFloat radius = ;
    CGFloat startA = ;
    CGFloat endA = ;

    for (int i = ; i < self.nums.count; i++) {
        NSNumber *data = self.nums[i];
        startA = endA;
        endA = startA + [data floatValue]/self.total *  * M_PI;
        UIBezierPath *path  = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
        [path addLineToPoint:center];

        CGFloat randRed = arc4random_uniform()/;
        CGFloat randGreen = arc4random_uniform()/;
        CGFloat randBlue = arc4random_uniform()/;

        UIColor *randomColor = [UIColor colorWithRed:randRed green:randGreen blue:randBlue alpha:];
        [randomColor set];
        [path fill];
    }

}
           

十.柱状图

quartz2D 的从零到一学习使用(附赠源码)

柱状图

-(NSArray *)nums{
    if (!_nums) {
        _nums = @[@,@,@,@,@,@];
    }
    return _nums;
}

//只有在drawRect方法中才能拿到图形上下文,才可以画图
- (void)drawRect:(CGRect)rect {

    //1.获取图形上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.绘制图像
    CGFloat margin = ;
    CGFloat width = (rect.size.width - (self.nums.count + )*margin)/self.nums.count;
    for (int i = ; i < self.nums.count; i++) {

        CGFloat x = margin + (width + margin)*i;
        CGFloat num = [self.nums[i] floatValue]/;
        CGFloat y = rect.size.height * (-num);
        CGFloat height = rect.size.height*num;

        CGRect rectA =  CGRectMake(x, y, width, height);
        CGContextAddRect(ctx,rectA);
        [[self randomColor] set];
        CGContextFillPath(ctx);
    }
}
           

十一.绘制图片

绘制文字和图片的时候,是不用去获取图像上下文的
quartz2D 的从零到一学习使用(附赠源码)

平铺

quartz2D 的从零到一学习使用(附赠源码)

固定位置画图

quartz2D 的从零到一学习使用(附赠源码)

默认固定大小

- (void)drawRect:(CGRect)rect {
    //剪切图片,超出的图片位置都要剪切掉!必须要在绘制之前写,否则无效
//    UIRectClip(CGRectMake(0, 0, 100, 50));

    UIImage *image = [UIImage imageNamed:@"1"];
    //(渲染)默认绘制的大小就是图片的大小,也可以自己设置一下

    //在什么范围下(原图大小)
    [image drawInRect:rect];
    //在哪个位置开始画
    [image drawAtPoint:CGPointMake(, )];
    //平铺
   [image drawAsPatternInRect:rect];
}
           

十二.绘制富文本

quartz2D 的从零到一学习使用(附赠源码)

苹果富文本

- (void)drawRect:(CGRect)rect {
    NSString *str = @"每个人都\n应该喜欢敲代\n码,只有这样,\n科技才能改变生活";
    //设置文字的属性
    NSMutableDictionary * paras = [NSMutableDictionary dictionary];
    paras[NSFontAttributeName] = [UIFont systemFontOfSize:];
    paras[NSForegroundColorAttributeName] = [UIColor redColor];
    paras[NSStrokeColorAttributeName] = [UIColor orangeColor];
    paras[NSStrokeWidthAttributeName] = @;
    //创建阴影对象
    NSShadow *shodow = [[NSShadow alloc] init];
    shodow.shadowColor = [UIColor blueColor];
    shodow.shadowOffset = CGSizeMake(, );
    shodow.shadowBlurRadius = ;
    //苹果的富文本就是这样搞出来的
    paras[NSShadowAttributeName]  = shodow;
    [str drawAtPoint:CGPointZero withAttributes:paras];
}
           

十三.自定义一个ImageView

quartz2D 的从零到一学习使用(附赠源码)

WXImageView继承自UIView

WXImageView.h

文件中定义的属性和接口和

UIImageView

一样,继承自

UIView

- (id)initWithImage:(UIImage *)image;
@property(nonatomic,strong)UIImage *image;
           

WXImageView.m

中方法的具体实现

- (id)initWithImage:(UIImage *)image{
    if (self = [super initWithFrame:CGRectMake(, , image.size.width, image.size.height)]) {
        _image = image;
    }
    return self;
}

- (void)setImage:(UIImage *)image{
    _image = image;
    [self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
    [self.image drawInRect:rect];
}
           

在vc中调用

WXImageView *imageV = [[WXImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
    imageV.center = CGPointMake(, );
    [self.view addSubview:imageV];
           
即使在xib使用,我发现和

UIImageView

的结果都是一样的

十四.雪花飘动

quartz2D 的从零到一学习使用(附赠源码)

雪花动画

//只有在drawRect方法中才能拿到图形上下文,才可以画图
- (void)drawRect:(CGRect)rect {
    //设置下雪的动画
    UIImage *image = [UIImage imageNamed:@"snow"];
    _snowY += ;

    [image drawAtPoint:CGPointMake(, _snowY)];
    if (_snowY >= rect.size.height) {
        _snowY = ;
    }
}
// 如果在绘图的时候需要用到定时器,通常使用CADisplayLink
// NSTimer很少用于绘图,因为调度优先级比较低,并不会准时调用
- (void)awakeFromNib
{
    // 创建定时器
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeChange)];

    // 添加主运行循环
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
// CADisplayLink:每次屏幕刷新的时候就会调用,屏幕一般一秒刷新60次
- (void)timeChange{
    [self setNeedsDisplay];
}
           
1.本质就是调用

drawRect

方法,一直刷新雪花的y值

2.每一次调用

drawRect

,都创建大量的对象,有人说可能性能不好,不过你可能多虑了,没吃都是在内存加载,不会创建新的

UIImage

十五.图形上下文栈

我自己详细的介绍了一下,上下文栈,可以看一下~

图形上下文详解

十六.图形上下文矩阵

到底是个啥?

就是图形上下文画出的东西永远是方方正正的,你要是想画个偏离的矩形,按照过去的方法画不出来,只能使用矩阵的方式。分别有偏移,缩放,旋转

quartz2D 的从零到一学习使用(附赠源码)

正常尺寸的椭圆

quartz2D 的从零到一学习使用(附赠源码)

x,y各自平移10px后的椭圆

quartz2D 的从零到一学习使用(附赠源码)

旋转后的椭圆

quartz2D 的从零到一学习使用(附赠源码)

缩放后的椭圆

- (void)drawRect:(CGRect)rect {
    //图形上下文矩阵
    //1.画一个椭圆
    CGContextRef ctx = UIGraphicsGetCurrentContext();
//    CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 100, 100));
    CGPathRef path = CGPathCreateWithEllipseInRect(CGRectMake(, , , ),nil);
    [[UIColor redColor] set];
    //1.偏移
//    CGContextTranslateCTM(ctx, 10, 10);
   //2.旋转
//    CGContextRotateCTM(ctx, M_PI_4);
    //3.缩放
    CGContextScaleCTM(ctx, , );
    CGContextAddPath(ctx, path);
    CGContextFillPath(ctx);
}
           
1.绘制变化的图形的步骤
  • 先绘制path
  • 设置图形上下文矩阵
  • 将path添加到图形上下文(这一步很重要,一定按照步骤来)
  • 渲染

2.绘图的时候,我们要使用底层的 CGPathRef,或者贝塞尔,然后 CGContextRef+path的方式。

如不这样,我注释的

CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 100, 100))

方法,这个方法中已经先去添加了path到图形上下文,即使我们在去添加图形上下文矩阵,也是无效

3.可以和图形上下文栈一起使用,给特定的一些图案设置一些属性,还有一些不会受到影响

本文特别感谢小马哥教育,参考了他们的视频,做的一个总结。文章的demo已经更新到了github,如有需要,可以参考

文/王鑫20111(简书作者)

原文链接:http://www.jianshu.com/p/f9f05e12ddd3#

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。