×

Quartz2D

96
竹间溪流
2016.09.01 17:14* 字数 2081

什么是Quartz2D

  • 是一个二维的绘图引擎,同时支持iOS和Mac系统
  • Quartz2D的API是纯C语言的,它的API来自于Core Graphics框架,数据类型和函数,都是以CG开头的

Quartz2D的应用

  • 画基本线条,绘制文字,图片,截图,自定义UIView
  • 在开发中,可以将内部结构比较复杂控件,通过绘制,实现自定义控件

Quartz2D中的图形上下文是什么,有哪些类型

  • 图形上下文是一个CGContextRef类型的数据
  • 图形上下文是用来保存用户绘制的内容状态,并决定绘制到那个地方
  • 图形上下文的类型:
    • Bitmap Graphics Context(位图上下文)
    • PDF Graphics Context
    • Window Graphics Context
    • Layer Graphics Context(图层上下文,自定义UIView取得上下文就是图层上下文.UIView之所以能够显示就是因为他内部有一个图层)
    • Printer Graphics Context

自定义UIView的步骤

  • 1.先自定义UIView
  • 2.实现DrawRect方法
  • 3.在DrawRect方法中取得跟View相关联的上下文
  • 4.绘制路径(描述路径长什么样).
  • 5.把描述好的路径保存到上下文(即:添加路径到上下文)
  • 6.把上下文的内容渲染到View
  • 注意:获取的上下文,必须是与要绘制到的view相关联的,这样才能将上下文的内容绘制到view上

基本线条的绘制

  • 通过drawRect方法绘制
  • DrawRect方法作用?什么时候调用.
    • 作用 : 专用在这个方法当中绘图的.只有在这个方法当中才能取得跟View相关联的上下文.
    • 调用 : 是系统自己调用的, 它是当View显示的时候自动调用.
    • 参数(rect): 指的是当前view的bounds
    • drawRect会自动创建一个跟当前View相关联的上下文.
  • View上的绘制以及显示
    在drawRect:方法中取得上下文后,就可以绘制东西到view上
    View内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics
    Context,因此,绘制的东西其实是绘制到view的layer上去了
    View之所以能显示东西,完全是因为它内部的layer

线段的绘制

  • 步骤:
    • 1.获取当前view的图形上下文
    • 2.描述路径
      • 2.1 设置路径的起点
      • 2.2 添加一根线到终点
    • 3.将绘制的路径添加到上下文中
    • 4.把上下文中的内容渲染到UIView的layer上(通过stoke或者fill的方式渲染)
-(void)drawRect:(CGRect)rect {
    //1.获取当前上下文(uigraphics开头)
    CGContextRef ctx =  UIGraphicsGetCurrentContext();

    //2.描述路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    //2.1设置起点
    [path moveToPoint:CGPointMake(50, 200)];
    //2.1添加一根线到终点
    [path addLineToPoint:CGPointMake(200, 50)];

    //3.把绘制的路径添加到上下文
    //UIBezierPath=UIKit ->   CGPathRef=coreGraphics(将UIKit类型的path转换为CGPathRef类型的path)
    CGContextAddPath(ctx, path.CGPath);
    
    //4.把上下文的内容渲染UIView的layer.(stoke(描边),fill(填充))
    CGContextStrokePath(ctx);   
}
  • 设置/修改上下文的状态.
设置线的宽度
CGContextSetLineWidth(ctx, 10);
设置线的连接样式.
CGContextSetLineJoin(ctx, kCGLineJoinBevel);
设置线的顶角样式
CGContextSetLineCap(ctx, kCGLineCapRound);
设置线的颜色.还可以直接用set这种方法
[[UIColor greenColor] set];   
  • 如果在添加另一根线
    • 第一种方法:重新设置起点,添加一根线到某个点,一个UIBezierPath路径上面可以有多条线.
    • 第二种方法:直接在原来的基础上添加线.把上一条线的终点当做下一条线的起点.添加一根线到某个点直接在下面addLineToPoint:CGPointMake(200, 50)

曲线的绘制

-(void)drawQuadCurve {
    //1.获取当前上下文(uigraphics开头)
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.描述路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 250)];
    //添加一根曲线
    [path addQuadCurveToPoint:CGPointMake(250, 250) controlPoint:CGPointMake(150, 50)];
    
    //3.把绘制的路径添加到上下文
    //UIBezierPath=UIKit ->   CGPathRef=coreGraphics
    CGContextAddPath(ctx, path.CGPath);
    
    //4.把上下文的内容渲染到View的layer
    CGContextStrokePath(ctx);
}

矩形的绘制

 //画矩形
- (void)drawRect {
    //1.获取上下文
    CGContextRef ctx =  UIGraphicsGetCurrentContext();

    //2.描述路径
    /*
    (x,y)点决定了矩形左上角的点在哪个位置
    (width,height)是矩形的宽度高度
    */
    // 普通的矩形 (bezierPathWithRect:)
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 200)];
    
    //圆角矩形 (bezierPathWithRoundedRect:cornerRadius:)
    //cornerRadius:圆角半径
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 200) cornerRadius:100];

    //单独设置某一角的圆角(bezierPathWithRoundedRect:byRoundingCorners:cornerRadius:)
    UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 100) byRoundingCorners:UIRectCornerTopLeft cornerRadius:CGSizeMake(10, 20)];    

    //设置线段的颜色
    [[UIColor yellowColor] set];
    //3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);
    //4.把上下文的内容渲染View的layer
    CGContextFillPath(ctx);
}

椭圆形的绘制

    //画椭圆形 bezierPathWithOvalInRect:
    //1.获取上下文
    CGContextRef ctf = UIGraphicsGetCurrentContext();
    //2.描述路径
    UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 200, 100)];
    //3.把路径添加到上下文
    CGContextAddPath(ctf, path.CGPath);
    //4.把上下文的内容渲染View的layer
    CGContextStrokePath(ctf);

弧形的绘制

//画弧
-(void)drawRect:(CGRect)rect
{
    //Center:圆心
    //radius:半径
    //startAngle:开始角度,0度在圆的最右侧.往下,角度为正,往上,角度为负
    //endAngle:截至角度
    //clockwise:是否为顺时针方向
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    
    CGFloat radius = rect.size.width * 0.5 - 10;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:NO];
    
    [path stroke];   
}

扇形的绘制

  • 绘制空心扇形
//先绘制一个弧形
    //设置弧形的中心点
    CGPoint center = CGPointMake(self.bounds.size.width * 0.5, rect.size.height * 0.5);
    //设置弧形的半径
    CGFloat radius = rect.size.width * 0.5 - 10;
    //绘制弧形曲线
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:NO];
//绘制扇形
    //添加一根线到圆心
    [path addLineToPoint:center];
    [[UIColor redColor] set];
    //关闭路径(自动从终点连接一根线到起点.)
    [path closePath];
    //渲染到view的layer上
    [path stroke];
  • 绘制实心扇形
    • 通过[path fill],这个填充方法会自动将路径关闭,并填充图形内部分
//先绘制一个弧形
    //设置弧形的中心点
    CGPoint center = CGPointMake(self.bounds.size.width * 0.5, rect.size.height * 0.5);
    //设置弧形的半径
    CGFloat radius = rect.size.width * 0.5 - 10;
    //绘制弧形曲线
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:NO];
//绘制扇形
    //添加一根线到圆心
    [path addLineToPoint:center];
    [[UIColor redColor] set];
    //填充模式会自动关闭路径
    [path fill];

图形的绘制以及渲染的方式

  • 1.通过获取图形上下文的方式,拼接路径,将路径添加到上下文,将上下文的内容渲染到View的Layer中
```
以弧形绘制的代码为例:
// 1.获取上下文
CGContextRef ctf = UIGraphicsGetCurrentContext();
// 2.绘制路径
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.width * 0.5) radius:100 startAngle:0 endAngle:M_PI clockwise:YES];
// 3.将路径添加到上下文
CGContextAddPath(ctf, path.CGPath);
// 4.将上下文中的内容渲染到View的Layer上
CGContextStrokePath(ctf);
```
  • 2.通过UIKit封装的方法进行上下文的画图
    直接来个: [path stroke];或者[path fill];就可以了.
    它底层的实现,就是获取上下文,拼接路径,把路径添加到上下文,渲染到View
    //绘制路径
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5); 
    CGFloat radius = rect.size.width * 0.5 - 10;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:NO];
    //渲染到view的layer上
    [path stroke];
    

绘制文字

  • 1.使用UIKit提供的方法进行绘制.
    方法说明:
    1. drawAtPoint:要画到哪个位置
    withAttributes:文本的样式.
    [str drawAtPoint:CGPointZero withAttributes:nil];

    2.  drawInRect:要将文字绘制到哪个区域
    withAttributes:文本的样式.
    [str drawInRect:rect withAttributes:nil];
    
  • 2.drawAtPoint:和drawInRect:的区别?
    drawAtPoint:不能够自动换行
    drawInRect:能够自动换行

    //1.设置要绘制的文字
    NSString * str = @"绘制文字练习绘制文字练习绘制文字练习绘制文字练习绘制文字练习";

    //2.设置文字的属性,通过Attribute设置
    NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
    //设置文字字体
    dict[NSFontAttributeName] = [UIFont systemFontOfSize:45];
    //设置文字颜色
    dict[NSForegroundColorAttributeName] = [UIColor yellowColor];
    //设置文字边的宽度
    dict[NSStrokeWidthAttributeName] = @5;
    //设置文字的描边
    dict[NSStrokeColorAttributeName] = [UIColor greenColor];
    //设置文字的阴影
    NSShadow * shadow = [[NSShadow alloc] init];
    shadow.shadowColor = [UIColor cyanColor];
    shadow.shadowOffset = CGSizeMake(10, 10);
    shadow.shadowBlurRadius = 10;
    dict[NSShadowAttributeName] = shadow;

    //3.将文字绘制到当前view 的layer上
    [str drawAtPoint:CGPointZero withAttributes:nil];
    [str drawInRect:rect withAttributes:dict];

绘制图片

绘制图片同样开始要先把图片素材导入.

  • 1.使用UIKit提供的方法进行绘制.
    方法说明:
    1. drawAtPoint:要画到哪个位置
    AtPoint:参数说明图片要绘制到哪个位置.
    通过调用UIKit的方法drawAtPoint:CGPointZero方法进行绘制;
    [str drawAtPoint:CGPointZero];

    2.  drawInRect:要将图片绘制到哪个区域
    AtPoint:参数说明图片要绘制到哪块区域.
    [str drawInRect:rect];
    
  • 在绘制图片过程当中.drawAtPoint:和drawInRect:两个方法的区别?
    drawAtPoint:绘制出来的图图片跟图片的实际尺寸一样大
    drawInRect:使用这个方法绘制出来的图片尺寸会和传入的rect区域一样大.

    //1.加载图片
    UIImage *image = [UIImage imageNamed:@"001"];

    //会把超过裁剪区域以外的内容给裁剪掉/必须得要在绘制之前设置裁剪区域
    UIRectClip(CGRectMake(0, 0, 50, 50));

    //drawAtPoint:绘制的是原始图片的尺寸大小.
    [image drawAtPoint:CGPointZero];
    //把绘制的图片填充到给的区域当中.
    [image drawInRect:rect];

    //平铺
    [image drawAsPatternInRect:rect];

    [[UIColor redColor] set];
    //快速填充一个区域
    UIRectFill(CGRectMake(50, 50, 100, 100));

模拟系统的UIImageView

  • 实现步骤
    1.自定义一个UIView
    2.定义一个UIImage公共的属性,用于接收外界传递的图片
    3.重写图片属性的set方法,每次传入图片后,进行重绘
    4.定义一个带有image参数的构造方法,在实现构造方法的时候,将view的size设置为image的size,并将image赋值给image属性,这样显示的 view就有了尺寸,并且尺寸和图片的尺寸相同
    5.在drawRect方法中,将外界传入的图片绘制到当前view的Layer上
#import <UIKit/UIKit.h>
@interface ZHJImageView : UIView

//用于接收外界传入的图片
@property (nonatomic, strong) UIImage * image;
//在初始化View的时候,将view的尺寸设置为传入的image的尺寸
-(instancetype)initWithImage:(UIImage *)image;

@end
@implementation ZHJImageView

//当image属性接收到传入的图片的时候,立刻调用drawRect方法,进行重绘
-(void)setImage:(UIImage *)image
{
    _image = image;
//重绘
    [self setNeedsDisplay];
}

//初始化的时候,让当前view的尺寸等于传入的图片的尺寸
-(instancetype)initWithImage:(UIImage *)image{
    if (self = [super init]) {
        //将图片赋值给image属性,并在属性的set方法中重绘
        _image = image;
        self.frame = CGRectMake(0, 0, self.image.size.width, self.image.size.height);
    }
    return self;
}

//将传入的图片绘制到view的layer上
-(void)drawRect:(CGRect)rect
{
    [self.image drawInRect:rect];
}

@end

CADisplayLink的使用(雪花飘落)

  • 在重绘时,NSTimer和CADisplayLink的区别
    setNeedsDisplay底层会调用DrawRect方法重绘.
    但是它不是立马就进行重绘.它仅仅是设置了一个重绘标志,等到下一次屏幕刷新(屏幕的刷新次数是每分钟60次)的时候才会调用DrawRect方法.

    如果使用NSTime的话,假设是0.01调用一次重绘.假设屏幕0.02秒的时候它才刷新一次.中间就会等0.01秒.
    也就是每次都会等0.01秒这样累加上去.让变的越来越卡顿.
    
    使用CADisplayLink的话,它的定时器方法就是屏幕每次刷新的时候就会调用(通常屏幕一秒钟刷新60次)
    它和setNeedsDisplay调用DrawRect方法的时机正好吻合,不会出间等待间隔.不会出现屏幕卡顿现象.
    
#import "SnowFlowView.h"
@implementation SnowFlowView
//因为这个view是从storyboard中加载的,所以将添加子控件的代码写在awakeFromNib中
-(void)awakeFromNib
{
    //创建定时器
    CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(flowSnow)];
    //将定时器加入到主线程中
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

//定义另个静态全局变量,用于保存图片位置
static int snowX = 0;
static int snowY = 0;
//定时器方法
-(void)flowSnow{
    //实现雪花下落的效果
    snowX += 1;
    if (snowX >= [UIScreen mainScreen].bounds.size.width) {
        snowX = 0;
    }
    snowY += 10;
    if (snowY >= [UIScreen mainScreen].bounds.size.height) {
        snowY = 0;
    }
    //重绘
    [self setNeedsDisplay];
}

//将雪花图片绘制到当前的view上
-(void)drawRect:(CGRect)rect
{
    UIImage * image = [UIImage imageNamed:@"雪花"];
    [image drawAtPoint:CGPointMake(snowX, snowY)];
}
@end

图形上下文状态栈

  • 我们获取的图层上下文当中其实有两块区域
    一个是存放添加的路径
    一个是用来保存用户设置的状态,这些状态包括线条的颜色,线宽等.
    当我们把上下文的内容渲染到View上面的时候,它会自动将设置的所有上下文状态运行到保存的路径上面显示到View上面.

  • 上下文状态栈
    上下文状态栈为内存中的一块区域,它用来保存当前上下文的状态,这里所谓的上下文的状态,其实就是在上下文中设置的线条的颜色,宽度等,当再次去出上下文的状态的时候,也就是取出了当时上下文中设置的线宽,颜色等属性给新绘制的线段使用

  • 如何存储和获取图形上下文的状态

    • 1.把上下文状态保存到上下文状态栈
      CGContextSaveGState(ctx);
    • 2.从上下文状态栈中取出上下文状态
      CGContextRestoreGState(ctx);
  • 图形上下文栈使用的示例

-(void)drawRect:(CGRect)rect
{
    //绘制第一条线
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIBezierPath * path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(20, 150)];
    [path addLineToPoint:CGPointMake(280, 150)];
    
    //第一次设置图形上下文的状态
    CGContextSetLineWidth(ctx, 10);
    [[UIColor cyanColor] set];
    CGContextSaveGState(ctx);//保存第一次设置的上下文的状态
    
    //第二次设置图形上下文的状态
    CGContextSetLineWidth(ctx, 5);
    [[UIColor greenColor] set];
    CGContextSaveGState(ctx);//保存第二次设置的上下文的状态
    
    //渲染第一次绘制的路径
    CGContextAddPath(ctx, path.CGPath);
    CGContextStrokePath(ctx);
    
    //绘制第二条线
    UIBezierPath * path1 = [[UIBezierPath alloc] init];
    [path1 moveToPoint:CGPointMake(150, 20)];
    [path1 addLineToPoint:CGPointMake(150, 280)];
    
    //从位图上下文状态栈中取出位图上下文的状态(取出第一次保存的位图上下文的状态)
    CGContextRestoreGState(ctx);//取出第二次图形上下文的状态
    CGContextRestoreGState(ctx);//取出第一次图像上下文的状态
    
    //渲染第二次绘制的路径
    CGContextAddPath(ctx, path1.CGPath);
    CGContextStrokePath(ctx);
}

图形上下文矩阵的操作(对上下文的路径进行的一些操作,如平移、旋转、缩放)

  • 使用注意点:
    1.想要做上下文的形变操作必须得获取上下文.
    2.形变操作要在添加路径之前进行.
    3.旋转不是以绘制的图形的中心旋转(有可能是以获取的上下文的左上角为原点进行旋转的,通过"CGContextRotateCTM(ctx, M_PI_4);"代码可以发现,旋转的是获取的图形上下文)
- (void)drawRect:(CGRect)rect {
    //获取当前的上下文.
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //画一个椭圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-100, -50, 200, 100)];

    //上下文的矩阵操作,就是可以上下文当中的路径进行一些形变操作.
    //平移操作
    CGContextTranslateCTM(ctx, 100, 50);
    //缩放操作
    CGContextScaleCTM(ctx, 0.5, 0.5);
    //旋转操作
    CGContextRotateCTM(ctx, M_PI_4);
   
    //注意形变操作要在添加路径之前进行.
    [[UIColor redColor]set];
    //把路径添加到上下文.
    CGContextAddPath(ctx, path.CGPath);
    //把上下文的内容渲染到View.
    CGContextFillPath(ctx);  
}
UI进阶
Web note ad 1