iOS-图形上下文栈、矩阵操作

80 阅读6分钟

一. 为什么使用图形上下文栈

1. 绘图的完整过程

  1. 程序启动,显示自定义的view,当程序第一次显示在我们眼前的时候,程序会调用drawRect:方法,在里面获取了图形上下文(在内存中拥有了),然后利用图形上下文保存绘图信息,可以理解为图形上下文中有一块区域用来保存绘图信息,有一块区域用来保存绘图状态(线宽,圆角,颜色),直线不是直接绘制到view上的,可以理解为在图形上下文中还有一块单独的区域用来先绘制图形,当调用渲染方法的时候,再把绘制好的图形显示到view上去。  
  2. 在绘制图形区域,会去保存绘图状态区域中查找对应的状态信息(线宽,圆角,颜色),然后在绘图区域把对第一条直线绘制完成。其实在渲染之前,就已经把直线在绘制图形区域画好了。

如图:1.png

2.png

3.png

调用渲染方法的时候,把绘制图形区域已经画好的图形直接显示到view上,就是我们看到的样子了。

  1. 画第二条的时候,如果没有对绘图状态进行重新设置,那么可以发现画第一条线的时候使用的绘图状态还保存在图形上下文中,在第二条线进行渲染之前,会根据第一条线(上一份绘图状态)对第二条线进行相应的设置,渲染后把第二条线显示到屏幕上。

4.png

2. 代码示例

- (void)drawRect:(CGRect)rect
{
    //获取上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();
    //绘图
    //第一条线
    CGContextMoveToPoint(ctx, 20, 100);
    CGContextAddLineToPoint(ctx, 100, 320);

    //设置第一条线的状态
    //设置线条的宽度
    CGContextSetLineWidth(ctx, 12);
    //设置线条的颜色
    [[UIColor brownColor]set];
    //设置线条两端的样式为圆角
    CGContextSetLineCap(ctx,kCGLineCapRound);
    //对线条进行渲染
    CGContextStrokePath(ctx);

    //第二条线
    CGContextMoveToPoint(ctx, 40, 200);
    CGContextAddLineToPoint(ctx, 80, 100);
    //渲染
    CGContextStrokePath(ctx);
}

效果图:

效果图.png

如果清空了状态,则在渲染之前,在绘制图形区域对第二条线进行绘制的时候,会去查找当前的绘图信息(已经更改——清空),根据绘图信息对第二条线进行绘制,调用渲染方法的时候把第二条线显示到view上。

代码示例:

- (void)drawRect:(CGRect)rect
{
    //获取上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();
    //绘图
    //第一条线
    CGContextMoveToPoint(ctx, 20, 100);
    CGContextAddLineToPoint(ctx, 100, 320);
    
    //设置第一条线的状态
    //设置线条的宽度
    CGContextSetLineWidth(ctx, 12);
    //设置线条的颜色
    [[UIColor brownColor]set];
    //设置线条两端的样式为圆角
    CGContextSetLineCap(ctx,kCGLineCapRound);
    //对线条进行渲染
    CGContextStrokePath(ctx);
    
    //第二条线
    CGContextMoveToPoint(ctx, 40, 200);
    CGContextAddLineToPoint(ctx, 80, 100);
    
    //清空状态
    CGContextSetLineWidth(ctx, 1);
    [[UIColor blackColor]set];
    CGContextSetLineCap(ctx,kCGLineCapButt);
    
    //渲染
    CGContextStrokePath(ctx);
}

效果图:

效果图.png

如果现在有新的需求,要求在这个基础上再画两条线,那就需要清空ctx中的状态很多次,很麻烦,为了解决这个问题,下面给大家介绍图形上下文栈。

二. 图形上下文栈

将当前的图形上下文状态copy一份到栈,这个栈就是图形上下文栈,利用图形上下文栈我们可以恢复绘图状态。

相关API:

CGContextSaveGState() //保存图形上下文状态使用
CGContextRestoreGState() //恢复图形上下文状态使用

CGContextRestoreGState不能一直调用,这要看图形上下文栈有多少图形上下文状态可恢复。

  1. 在获取图形上下文之后,通过  CGContextSaveGState(ctx);  方法,把当前获取的上下文拷贝一份,保存一份最纯洁的图形上下文。
  2. 在画第二条线之前,使用CGContextRestoreGState(ctx);方法,还原开始的时候保存的那份最纯洁的图形上下文。

代码:

- (void)drawRect:(CGRect)rect
{
    //获取上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();
    
    //保存一份最初的图形上下文
    CGContextSaveGState(ctx);
    
    //绘图
    //第一条线
    CGContextMoveToPoint(ctx, 20, 100);
    CGContextAddLineToPoint(ctx, 100, 320);
    
    //设置第一条线的状态
    //设置线条的宽度
    CGContextSetLineWidth(ctx, 12);
    //设置线条的颜色
    [[UIColor brownColor]set];
    //设置线条两端的样式为圆角
    CGContextSetLineCap(ctx,kCGLineCapRound);
    //对线条进行渲染
    CGContextStrokePath(ctx);
    
    //还原开始的时候保存的那份最纯洁的图形上下文
    CGContextRestoreGState(ctx);
    
    //第二条线
    CGContextMoveToPoint(ctx, 40, 200);
    CGContextAddLineToPoint(ctx, 80, 100);
    
    //清空状态
//    CGContextSetLineWidth(ctx, 1);
//    [[UIColor blackColor]set];
//    CGContextSetLineCap(ctx,kCGLineCapButt);
    
    //渲染
    CGContextStrokePath(ctx);
}

效果图和上面一样,这样就不用一直清空状态了。

**注意:**在栈里保存了几次,那么就可以取几次(比如不能保存了一次,取两次,在取第二次的时候,栈里为空会直接挂掉)。

三. 矩阵操作

利用矩阵操作,我们可以把画出的图形进行旋转、缩放、平移操作。

旋转: CGContextRotateCTM() 缩放: CGContextScaleCTM() 平移: CGContextTranslateCTM()

示例:先画个正方形和圆形

- (void)drawRect:(CGRect)rect
{
    //画四边形
    //获取图形上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();

    //绘图
    //画正方形
    CGContextAddRect(ctx, CGRectMake(20, 50, 100, 100));
    //画一个圆
    CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));

    //渲染
    CGContextStrokePath(ctx);
}

未旋转.png

1. 旋转

方法:

CGContextRotateCTM(<#CGContextRef c#>, <#CGFloat angle#>)
//接收两个参数(图形上下文,弧度)

**注意:**设置矩阵操作必须要在添加绘图信息之前,如果设置在添加绘图信息之后的话,此时它已经画完了,就无效了。

示例:

- (void)drawRect:(CGRect)rect
{
    //画四边形
    //获取图形上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();

    //矩阵操作, 旋转45度
    //注意点:设置矩阵操作必须要在添加绘图信息之前
    CGContextRotateCTM(ctx, M_PI_4);
     
    //绘图
    //画正方形
    CGContextAddRect(ctx, CGRectMake(20, 50, 100, 100));
    //画一个圆
    CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
    //渲染
    CGContextStrokePath(ctx);
}

旋转后.png

**提示:**旋转的时候,是整个layer都旋转了。

2. 缩放

方法:

CGContextScaleCTM(<#CGContextRef c#>, <#CGFloat sx#>, <#CGFloat sy#>)
//接收三个参数(图形上下文,x方向的缩放比例,y方向上的缩放比例

示例:

- (void)drawRect:(CGRect)rect
{
    //画四边形
    //获取图形上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();

    //缩放,x方向缩放0.5倍,y方向缩放1.5倍
    //注意点:设置矩阵操作必须要在添加绘图信息之前
    CGContextScaleCTM(ctx, 0.5, 1.5);
      
    //绘图
    //画正方形
    CGContextAddRect(ctx, CGRectMake(20, 50, 100, 100));
    //画一个圆
    CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
    //渲染
    CGContextStrokePath(ctx);
}

缩放.png

3. 平移

方法:

CGContextTranslateCTM(<#CGContextRef c#>, <#CGFloat tx#>, <#CGFloat ty#>)
//接收三个参数(图形上下文,x方向的偏移量,y方向上的偏移量)

示例:

- (void)drawRect:(CGRect)rect
{
    //画四边形
    //获取图形上下文
    CGContextRef ctx=UIGraphicsGetCurrentContext();

    //平移,x方向移动50,y方向移动100
    //注意点:设置矩阵操作必须要在添加绘图信息之前
    CGContextTranslateCTM(ctx, 50, 100);
      
    //绘图
    //画正方形
    CGContextAddRect(ctx, CGRectMake(20, 50, 100, 100));
    //画一个圆
    CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
    //渲染
    CGContextStrokePath(ctx);
}

平移.png