iOS开发之自定义view—Quartz2D

来源:互联网 发布:文艺气息的网名知乎 编辑:程序博客网 时间:2024/05/16 14:08

之前的文章介绍了数据的传递和存储,这篇文章就介绍显示数据的控件。iPhone之所以这么受欢迎,其中有一个原因是因为它的UI界面非常美观。iOS提供了UIKit框架,里面有各种各样的UI控件。

这里有利用Quartz2D生成的图片(包括裁剪圆形图片,截图,水印图片):https://github.com/shihuaixing/ClipCircleImageDemo.git

UILabel:显示文字

UIImageVIew:显示图片

UIButton:同时显示图片和文字,还能点击

等等。

这些控件进行拼拼凑凑是能够搭建和实现一些简单、常见的UI界面,并显示数据的。

但是有些UI界面极其复杂、而且比较有个性,用普通的UI控件是无法实现的,这个时候可以利用Quartz2D技术奖控件内部的结构画出来,就向自定义控件的样子。

其实,iOS中大部分控件的内容都是通过Quarz2D画出来的。

因此,Quarz2D在iOS开发中很重要的一个价值就是:自定义view(自定义控件)。

我们来简单了解一下关于Quarz2D的知识。

Quarz2D:是一个二维绘图引擎,同时支持iOS和Mac系统。

Quarz2D的API是纯C语言的。

Quarz2D的API来自于Core Graphics框架

Quarz2D函数类型和函数基本都以CG为前缀:

CGContextRef

CGPathRef

CGContextStrokePath(ctx)

Quarz2D能完成的简单的工作:

1) 绘制图形:线条/三角形/矩形/圆/弧等

2) 绘制文字

3) 绘制/生成图片

4)读取/生成PDF

5)图/裁剪图片

6)定义控件

7)等等

下面来看一下利用Quarz2D实现的一些效果:

1、图片裁剪:

2、手势解锁:

下面我们在使用Quarz2D之前,简单认识一些概念:

1)图形上下文(Graphics Context):是一个CGContextRef类型的数据

a.    作用:

保存绘图信息,绘图状态;

决定绘制的输出目标(绘制到什么地方)

(输出目标客源是PDF文件、Bitmap或者显示器的窗口上)

b.    Quarz2D提供了以下几种类型的Graphics Context:

·  Bitmap Graphics Context

·  PDF Graphics Context

·  Window Graphics Context

·  Layer Graphics Context

·  Printer Graphics Context


注意:图形上下文(Graphics Context)是一个很重要的类型!

了解了图形上下文(GraphicsContext),那我们如何利用Quarz2D自定义view(自定义控件)?

如何利用Quarz2D绘制内容到view上:

1)首先,得有图形上下文,因为它能保存绘图信息,并且决定这绘制到什么地方去

2)其次,那个图形上下文必须跟view想关联,才能讲内容绘制到view上

自定义view的步骤:

1)新建一个类,继承自UIView

2)实现-(void)drawRect:方法,然后在这个方法中

a.    取得跟当前view相关联的图形上下文

b.    绘制相应的图形内容

c.    利用图形上下文将绘制的所有内容渲染显示到view上

那大家可能会要问,为什么要实现drawRect:方法才能将绘图显示到view上呢?最重要的一点:因为在drawRect:方法中才能取到跟view相关联的图形上下文。

drawRect:方法在什么时候被执行呢?

了解:

1)view内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer GraphicsContext,因此,绘制的内容其实是绘制到viewlayer上去了。

2)view之所以能显示东西,完全是因为它内部的layer(图层)

下面通过代码显示Quarz2D的功能:

创建新项目,在控制器view上拖一个自定义view(redView),创建自定义view类(HXView)。将自定义的view的类设置为HXView。

1、画线、画圆、画弧

- (void)drawBasisGraphics {        /***************画线*****************/    // 获得图形上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 画线    // 1.起点    CGContextMoveToPoint(ctx, 10, 10);    // 添加线段    CGContextAddLineToPoint(ctx, 100, 100);            CGContextAddLineToPoint(ctx, 200, 30);        // 设置线宽    CGContextSetLineWidth(ctx, 10);    // 设置线颜色    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);    // 设置两头部样式    CGContextSetLineCap(ctx, kCGLineCapRound);    // 设置连接处样式    CGContextSetLineJoin(ctx, kCGLineJoinRound);        // 渲染并显示到view上    CGContextStrokePath(ctx);                /***************画圆*****************/        // 1.通过椭圆画出圆    // 1)(不填充)//    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));//    //    // 设置线宽//    CGContextSetLineWidth(ctx, 10);//    // 设置线颜色//    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);//    //    // 渲染并显示到view上//    CGContextStrokePath(ctx);        // 2)(填充)//    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));//    //    // 设置线颜色//    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);//    //    // 渲染并显示到view上//    CGContextFillPath(ctx);            // 2.根据圆点、半径(可以画圆弧)    // 1)不填充    CGContextAddArc(ctx, 100, 100, 50, 0, M_PI_2, 0);    // CGContextAddArc: 参数说明    /*     CGContextRef c:图形上下文     CGFloat x, CGFloat y:圆点坐标     CGFloat radius:圆的半径     CGFloat startAngle:画圆的开始角度(逆时针为负,顺时针为正)     CGFloat endAngle:画圆的结束角度     int clockwise:画图方向,0:顺时针,1:逆时针     */    // 设置线颜色    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);        // 渲染并显示到view上    CGContextStrokePath(ctx);        // 2)填充//    CGContextAddArc(ctx, 100, 100, 50, 0, M_PI_2, 0);//    // 设置线颜色//    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);//    //    // 渲染并显示到view上//    CGContextFillPath(ctx);            /***************画弧*****************/    // 当前点    CGContextMoveToPoint(ctx, 50, 100);    CGContextAddCurveToPoint(ctx, 0, 0, 100, 100, 50, 0);        // set : 同时设置为实心和空心颜色    // setStroke : 设置空心颜色    // setFill : 设置实心颜色    [[UIColor whiteColor] set];        CGContextStrokePath(ctx);        }

2、画图,并裁剪

// 画图- (void)drawImage:(CGRect)rect {    // 获得图形上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        UIImage *image = [UIImage imageNamed:@"008.jpg"];    //    [image drawAsPatternInRect:rect];// 平铺//    [image drawInRect:rect]; // 拉伸    [image drawAtPoint:CGPointMake(0, 0)];// 从父控件的0,0点开始画图        // 渲染显示到view上    CGContextStrokePath(ctx);}

// 裁剪圆形图片- (void)drawCircleImage:(CGRect)rect {    // 获得图形上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        UIImage *image = [UIImage imageNamed:@"008.jpg"];        // 1.画圆    CGContextAddEllipseInRect(ctx, CGRectMake((rect.size.width - image.size.width) * 0.5, (rect.size.height - image.size.height) * 0.5, image.size.height, image.size.width));    // 超出该圆的内容全部裁剪掉    CGContextClip(ctx);// 裁剪当前图形上下文    CGContextClipToRect(ctx, CGRectMake((rect.size.width - image.size.width) * 0.5, (rect.size.height - image.size.height) * 0.5, image.size.height, image.size.width)); // 裁剪范围        // 画图    [image drawAtPoint:CGPointMake((rect.size.width - image.size.width) * 0.5, (rect.size.height - image.size.height) * 0.5)];        CGContextSetLineWidth(ctx, 10);    // 渲染显示到view上    CGContextStrokePath(ctx);}

3、画文字(使用Quartz2D画出来的文字是反着的,跟坐标系有关)

- (void)drawText:(CGRect)rect {        NSString *textString = @"哈哈哈哈shx";    NSMutableDictionary *attris = [NSMutableDictionary dictionary];    attris[NSFontAttributeName] = [UIFont systemFontOfSize:20];    attris[NSForegroundColorAttributeName] = [UIColor whiteColor];    [textString drawInRect:rect withAttributes:attris];}

下面我们来做一个画板:


清空:清除画板上的所有内容;

回退:清除在画板上画出的最后一条线

 

创建新项目,在控制器view上拖一个自定义view(redView),创建自定义view类(HXView)。将自定义的view的类设置为HXView。

因为手指要在view上触摸才能有事件发生,所以要在HXView.m文件中重写如下方法:

/** *  手指开始触摸view的时候调用 *  在该方法中可以确定线的起点 */- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}/** *  手指在view上移动时调用 *  在该方法中连接其他点 */- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    }/** *  手指触摸结束时候调用 *  在该方法中处理一条路径画完之后的事情 */- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    }

先来了解一下思路:

每当手指开始触摸view的时候就会调用touchesBegan方法,每次调用该方法时就创建一个新的路径(pathn个点组成),把touchesBegan获得的点(起点)添加到这条新的路径中(数组存储点);当手指在view上移动的时候就会调用touchesMoved方法,每次调用该方法就会返回手指所在的位置,把该位置添加到path中;当手指在view上触摸结束的时候就会调用touchesEnded方法,在该方法中项path中添加最后的一个点。至此,path路径上的所有点都被存储在path中了。现在可以利用viewdrawRect:方法开始绘制路径了。

           由于画板可能会绘制很对条路径,所以我们也要把所有的路径存起来。(数组存储路径)

注意:每次向path中添加点的时候都要主动调用setNeedsDisplay方法,该方法会调用drawRect:方法随时绘制路径。

现在来实现这些方法

在touchesBegan方法中,能获得手指开始触摸时候的位置(一个路径的起点)

/** *  手指开始触摸view的时候调用 *  在该方法中可以确定线的起点 */- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {        // 获得开始触摸的点(起点)    CGPoint startPoint = [self pointWithTouch:touches];    // 存储新路径上的所有的点    NSMutableArray *currentPath = [NSMutableArray array];        // 新路径存储新路径的第一个点(新路径起点)    [currentPath addObject:[NSValue valueWithCGPoint:startPoint]];    // 将新路径存储到数组中    [self.paths addObject:currentPath];        // 重新绘制    [self setNeedsDisplay];}

获得点的方法

- (CGPoint)pointWithTouch:(NSSet * )touches {    UITouch *touch = [touches anyObject];    CGPoint point = [touch locationInView:touch.view];    return point;}

手指开始移动:

/** *  手指在view上移动时调用 *  在该方法中连接其他点 */- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {        // 获取其他点    CGPoint otherPoint = [self pointWithTouch:touches];            // 取出最后一个路径(当前正在画的路径)    NSMutableArray *currentPath = [self.paths lastObject];    // 存储其他点    [currentPath addObject:[NSValue valueWithCGPoint:otherPoint]];        // 重新绘制    [self setNeedsDisplay];    }

手指触摸事件结束:(考虑为什么只要调用touchesMoved方法)

/** *  手指触摸结束时候调用 *  在该方法中处理一条路径画完之后的事情 */- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {        // 手指离开后,也是连接点    [self touchesMoved:touches withEvent:event];    }

回退功能(移除存储路径的数组的最后一个元素):

- (void)revoke {    [self.paths removeLastObject];    [self setNeedsDisplay];   }

清空功能(移除存储路径的数组的所有元素);

- (void)clear {    [self.paths removeAllObjects];    [self setNeedsDisplay];}
注意:每次改变路径内容的时候都要调用setNeedsDisplay方法!

效果:


回退:

清空:

只要自定义一个view,实现它的drawRect:方法,就可以在这个view上画出你想要的内容。大家可以试着实现以下前途的手势解锁功能的界面。(以上部分图片引用自mj老师)




0 0
原创粉丝点击