绘图

作者: Saxon_Geoffrey | 来源:发表于2015-03-12 23:22 被阅读13994次

    视图可以通过子视图、图层或实现drawRect:方法来表现内容,如果说实现了drawRect:方法,那么最好就不要混用其他方法了,如图层和子视图。自定义绘图大部分是由UIKit或者Core Graphics来实现的。现在我们来讲讲UIBezierPath和Core Graphics。

    1.UIBezierPath

    UIKit中的UIBezierPath是Core Graphics框架关于path的一个封装。可以创建基于矢量的路径,例如椭圆或者矩形,或者有多个直线和曲线段组成的形状。

    1.绘制矩形

    绘制矩形最简单的办法是使用UIRectFrame和UIRectFill,如下

    - (void)drawRect:(CGRect)rect
    {   
        [[UIColor redColor]setFill];
        UIRectFill(CGRectMake(20, 20, 100, 50));
    }
    

    其中UIColor setFill是设置画笔颜色。

    通过使用UIBezierPath可以自定义绘制线条的粗细,是否圆角等。

    - (void)drawRect:(CGRect)rect
    {
        UIColor *color = [UIColor colorWithRed:0 green:0 blue:0.7 alpha:1];
        [color set]; //设置线条颜色
        
        UIBezierPath* aPath = [UIBezierPath bezierPathWithRect:CGRectMake(20, 20, 100, 50)];
        
        aPath.lineWidth = 8.0;
        aPath.lineCapStyle = kCGLineCapRound; //线条拐角
        aPath.lineJoinStyle = kCGLineCapRound; //终点处理
        
        [aPath stroke];
    }
    

    效果如下:

    2.圆和椭圆

    只有将上面代码中的:

    UIBezierPath* aPath = [UIBezierPath bezierPathWithRect:CGRectMake(20, 20, 100, 50)];
    

    替代为以下代码即可绘制一个圆形

    UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, 100, 100)];
    

    椭圆:

    UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, 100, 50)];
    

    3.多边形

    多边形是一些简单的形状,这些形状是由一些直线线条组成,我们可以用moveToPoint: 和 addLineToPoint:方法去构建。moveToPoint:设置我们想要创建形状的起点。从这点开始,我们可以用方法addLineToPoint:去创建一个形状的线段。
    我们可以连续的创建line,每一个line的起点都是先前的终点,终点就是指定的点。
    closePath可以在最后一个点和第一个点之间画一条线段。

    - (void)drawRect:(CGRect)rect
    {
        UIColor *color = [UIColor colorWithRed:0 green:0.7 blue:0 alpha:1];
        [color set];
        
        UIBezierPath* aPath = [UIBezierPath bezierPath];
        aPath.lineWidth = 5.0;
        
        aPath.lineCapStyle = kCGLineCapRound;
        aPath.lineJoinStyle = kCGLineCapRound;
        
        // 起点
        [aPath moveToPoint:CGPointMake(100.0, 0.0)];
        
        // 绘制线条
        [aPath addLineToPoint:CGPointMake(200.0, 40.0)];
        [aPath addLineToPoint:CGPointMake(160, 140)];
        [aPath addLineToPoint:CGPointMake(40.0, 140)];
        [aPath addLineToPoint:CGPointMake(0.0, 40.0)];
        [aPath closePath];//第五条线通过调用closePath方法得到的
        
        //根据坐标点连线
        [aPath stroke];
        [aPath fill];
    }
    

    效果如下:

    4.不规则形状

    想画弧线组成的不规则形状,我们需要使用中心点、弧度和半径,如下图。弧度使用顺时针脚底,0弧度指向右边,pi/2指向下方,pi指向左边,-pi/2指向上方。然后使用bezierPathWithArcCenter: radius: startAngle endAngle: clockwise:方法来绘制。

    1).绘制一段弧度

    - (void)drawRect:(CGRect)rect
    {
        UIColor *color = [UIColor redColor];
        [color set]; //设置线条颜色
        
        UIBezierPath* aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(80, 80)
                                                             radius:75
                                                         startAngle:0
                                                           endAngle:DEGREES_TO_RADIANS(135)
                                                          clockwise:YES];
        
        aPath.lineWidth = 5.0;
        aPath.lineCapStyle = kCGLineCapRound; //线条拐角
        aPath.lineJoinStyle = kCGLineCapRound; //终点处理
        
        [aPath stroke];
    }
    

    结果如下:

    2).两种贝塞尔曲线

    a).第一种:
    - (void)drawRect:(CGRect)rect
    {
        UIColor *color = [UIColor redColor];
        [color set]; //设置线条颜色
        
        UIBezierPath* aPath = [UIBezierPath bezierPath];
        
        aPath.lineWidth = 5.0;
        aPath.lineCapStyle = kCGLineCapRound; //线条拐角
        aPath.lineJoinStyle = kCGLineCapRound; //终点处理
        
        [aPath moveToPoint:CGPointMake(20, 100)];
        
        [aPath addQuadCurveToPoint:CGPointMake(120, 100) controlPoint:CGPointMake(70, 0)];
        
        [aPath stroke];
    }
    

    效果如下:

    b).第二种:
    - (void)drawRect:(CGRect)rect
    {
        UIColor *color = [UIColor redColor];
        [color set]; //设置线条颜色
        
        UIBezierPath* aPath = [UIBezierPath bezierPath];
        
        aPath.lineWidth = 5.0;
        aPath.lineCapStyle = kCGLineCapRound; //线条拐角
        aPath.lineJoinStyle = kCGLineCapRound; //终点处理
        
        [aPath moveToPoint:CGPointMake(5, 80)];
        
        [aPath addCurveToPoint:CGPointMake(155, 80) controlPoint1:CGPointMake(80, 0) controlPoint2:CGPointMake(110, 100)];
        
        [aPath stroke];
    }
    

    3).曲线组合:

    下面我们来画一朵花:

    - (void)drawRect:(CGRect)rect {
        CGSize size = self.bounds.size;
        CGFloat margin = 10;
        //rintf:四舍五入函数
        CGFloat radius = rintf(MIN(size.height - margin, size.width - margin) / 4);
        CGFloat xOffset, yOffset;
        
        CGFloat offset = rintf((size.height - size.width) / 2);
        if (offset > 0) {
            xOffset = (CGFloat)rint(margin / 2);
            yOffset = offset;
        } else {
            xOffset = -offset;
            yOffset = rintf(margin / 2);
        }
        
        [[UIColor redColor] setFill];
        UIBezierPath *path = [UIBezierPath bezierPath];
        
        [path addArcWithCenter:CGPointMake(radius * 2 + xOffset, radius + yOffset)
                        radius:radius
                    startAngle:(CGFloat)-M_PI
                      endAngle:0
                     clockwise:YES];
        [path addArcWithCenter:CGPointMake(radius * 3 + xOffset, radius * 2 + yOffset)
                        radius:radius
                    startAngle:(CGFloat)-M_PI_2
                      endAngle:(CGFloat)M_PI_2
                     clockwise:YES];
        [path addArcWithCenter:CGPointMake(radius * 2 + xOffset, radius * 3 + yOffset)
                        radius:radius
                    startAngle:0
                      endAngle:(CGFloat)M_PI
                     clockwise:YES];
        [path addArcWithCenter:CGPointMake(radius + xOffset, radius * 2 + yOffset)
                        radius:radius
                    startAngle:(CGFloat)M_PI_2
                      endAngle:(CGFloat)-M_PI_2
                     clockwise:YES];
        [path closePath];
        [path fill];
    }
    

    记得调用以下这个方法,使其view变化后(例如横屏了)重新调用drawRect:

    - (void)awakeFromNib {
        // Comment this line to see default behavior
        self.contentMode = UIViewContentModeRedraw;
    }
    

    2.CoreGraphics

    上面我们讲过,UIBezierPath是CoreGraphics的封装,使用它可以完成大部分的绘图操作,不过更底层的CoreGraphics更加强大。

    CoreGraphics,也称为Quartz 2D 是UIKit下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics数据结构和函数可以通过前缀CG来识别。

    由于像素是依赖于目标的,所以2D绘图并不能操作单独的像素,我们可以从上下文(Context)读取它。所以我们在绘制之前需要通过

    CGContextRef ctx = UIGraphicsGetCurrentContext()
    

    获取当前推入堆栈的图形,相当于你所要绘制图形的图纸,然后绘图就好比在画布上拿着画笔机械的进行画画,通过制定不同的参数来进行不同的绘制。

    画完之后我们需要通过

    CGContextSetFillColorWithColor(CGContextRef c, CGColorRef color)
    
    CGContextFillPath(CGContextRef c)
    

    来填充颜色并完成最后的绘制。下面我们来完成和UIBezierPath一样的绘制。

    1.绘制矩形

    绘制矩形需要先定义矩形的rect,然后使用

    CGContextAddRect(CGContextRef c, CGRect rect)
    

    进行绘制即可。如下:

    - (void)drawRectangle {
        CGRect rectangle = CGRectMake(80, 400, 160, 60);
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        
        CGContextAddRect(ctx, rectangle);
        CGContextSetFillColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
    
        CGContextFillPath(ctx);
    }
    

    如下:

    2.圆和椭圆

    我们使用下面这个方法来绘制弧线:

    CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
    

    其中的参数说明如下:

    c           当前图形
    x           圆弧的中心点坐标x
    y           曲线控制点的y坐标
    radius      指定点的x坐标值
    startAngle  弧的起点与正X轴的夹角,
    endAngle    弧的终点与正X轴的夹角
    clockwise   指定1创建一个顺时针的圆弧,或是指定0创建一个逆时针圆弧
    

    所以我们可以通过下面创建圆形:

    - (void)drawCircleAtX:(float)x Y:(float)y {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextAddArc(ctx, x, y, 150, 0, 2 * M_PI, 1);
        CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
        CGContextFillPath(ctx);
    }
    

    现在看起来:

    屏幕快照 2015-03-14 上午12.10.07.png

    绘制椭圆我们需要先给定一个容纳椭圆的矩形,然后使用

    CGContextAddEllipseInRect(CGContextRef context, CGRect rect)
    

    进行绘制,如下:

    - (void)drawEllipseAtX:(float)x Y:(float)y {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGRect rectangle = CGRectMake(x, y, 60, 30);
        CGContextAddEllipseInRect(ctx, rectangle);
        CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
        CGContextFillPath(ctx);
    }
    

    现在看起来:

    3.多边形

    绘制多边形需要通过CGContextMoveToPoint从一个开始点开始一个新的子路径,然后通过CGContextAddLineToPoint在当前点追加直线段,最后通过CGContextClosePath关闭路径即可。如下我们绘制一个三角形:

    - (void)drawTriangle {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextBeginPath(ctx);
        
        CGContextMoveToPoint(ctx, 160, 40);
        CGContextAddLineToPoint(ctx, 190, 80);
        CGContextAddLineToPoint(ctx, 130, 80);
        CGContextClosePath(ctx);
        
        CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
        CGContextFillPath(ctx);
    }
    

    现在看起来:

    屏幕快照 2015-03-14 上午12.18.00.png

    4.不规则形状

    1).绘制一段弧度:[self drawCurve];

    a).第一种:和贝塞尔曲线中的第一种一样,我们同样需要给定起始点
    CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)
    

    给定控制点和终点:

    CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y)
    

    其中:

    cpx: 曲线控制点的x坐标
    cpy: 曲线控制点的y坐标
    
    - (void)drawQuadCurve {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextBeginPath(ctx);
        
        CGContextMoveToPoint(ctx, 50, 130);
        CGContextAddQuadCurveToPoint(ctx, 0, 100, 25, 170);
        
        CGContextSetLineWidth(ctx, 10);
        CGContextSetStrokeColorWithColor(ctx, [UIColor brownColor].CGColor);
        CGContextStrokePath(ctx);
    }
    

    我们画两个如上的曲线,现在看起来:

    b).第二种:

    第二种我们需要给两个控制点:

    - (void)drawCurve2{
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextBeginPath(ctx);
        
        CGContextMoveToPoint(ctx, 170, 170);
        CGContextAddCurveToPoint(ctx, 160, 250, 230, 250, 160, 290);
        
        CGContextSetLineWidth(ctx, 10);
        CGContextSetStrokeColorWithColor(ctx, [UIColor brownColor].CGColor);
        CGContextStrokePath(ctx);    
    }
    

    现在看起来:

    还不错。

    5.加阴影效果

    可以通过

    CGContextSetShadowWithColor(CGContextRef context, CGSize offset, CGFloat blur, CGColorRef color)
    

    设置阴影效果,4个参数分别是图形上下文,偏移量(CGSize),模糊值,和阴影颜色。我们在画圆圈的方法中加入它:

    - (void)drawCircleAtX:(float)x Y:(float)y {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextAddArc(ctx, x, y, 150, 0, 2 * M_PI, 1);
        CGContextSetShadowWithColor(ctx, CGSizeMake(10, 10), 20.0f, [[UIColor grayColor] CGColor]);
        CGContextSetFillColorWithColor(ctx, [UIColor yellowColor].CGColor);
        CGContextFillPath(ctx);
    }
    

    注意,它除了会在会在边缘绘制阴影效果,还会在有子控件的地方绘制,如下:

    6.渐变色效果

    1)放射式渐变:CGContextDrawRadialGradient

    放射式渐变以某种颜色从一点开始,以另一种颜色在其它点结束。它看起来会是一个圆。

    为了创建一个放射式渐变,你要调用CGGradientCreateWithColors函数。这个函数的返回值是一个新的类型为CGGradientRef的渐变。

    CGGradientCreateWithColors包含以下3个参数:

    Color Space:这是一个色彩范围的容器,类型是CGColorSpaceRef. 这个参数,我们可以传入CGColorSpaceCreateDeviceRGB函数的返回值,它将给我们一个RGB色彩空间。
    颜色分量的数组:这个数组必须包含颜色的数组值。
    位置数组:颜色数组中各个颜色的位置,此参数控制该渐变从一种颜色过渡到另一种颜色的速度有多快。

    如下:

    - (void)drawdrawRadialGradientWithRect:(CGRect)rect
    {
    //先创造一个CGGradientRef,颜色是白,黑,location分别是0,1
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        NSArray* gradientColors = [NSArray arrayWithObjects:
                                   (id)[UIColor whiteColor].CGColor,
                                   (id)[UIColor blackColor].CGColor, nil];
        CGFloat gradientLocations[] = {0, 1};
        
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
                                                            (__bridge CFArrayRef)gradientColors,
                                                            gradientLocations);
        CGPoint startCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
        CGFloat radius = MAX(CGRectGetHeight(rect), CGRectGetWidth(rect));
    

    调用完上面那个函数后,我们需要使用:

    CGContextDrawRadialGradient(CGContextRef context, CGGradientRef gradient, CGPoint startCenter, CGFloat startRadius, CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options)
    

    进行上下文绘制,参数说明如下:

    CGPoint startCenter:白色的起点(中心圆点)
    CGFloat startRadius:起点的半径,这个值多大,中心就是多大一块纯色的白圈
    CGPoint endCenter:白色的终点, 可以和起点一样,不一样的话就像探照灯一样从起点投影到这个终点
    CGFloat endRadius:终点的半径,

    如下:

    CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextDrawRadialGradient(context, gradient,
                                    startCenter, 0,
                                    startCenter, radius,
                                    0);
        
        CGGradientRelease(gradient);
        CGColorSpaceRelease(colorSpace);
    }
    

    我们这样调用它:

    [self drawdrawRadialGradientWithRect:CGRectMake(120, 510, 60, 60)];
    

    效果如下;

    2)线性渐变:CGGradientCreateWithColorComponents

    线性渐变以某种颜色从一点开始,以另一种颜色在其它点结束。

    你先要调用上面讲到的drawdrawRadialGradientWithRect 函数去创建一个gradient渐变,创建好gradient后,我们将使用

    CGContextDrawLinearGradient(CGContextRef context, CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions options)
    

    在图形上下文中绘制,此过程需要五个参数, 比上面的辐射渐变多了最后一个参数:

    Gradient drawing options :指定当你的起点或者终点不在图形上下文的边缘内时该如何处理。你可以使用你的开始或结束颜色来填充渐变以外的空间。此参数为以下值之一:
    KCGGradientDrawsAfterEndLocation扩展整个渐变到渐变的终点之后的所有点, KCGGradientDrawsBeforeStartLocation扩展整个渐变到渐变的起点之前的所有点。
    0不扩展该渐变。

    代码如下:

    - (void)drawingLinearGradientWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint
    {
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        NSArray* gradientColors = [NSArray arrayWithObjects:
                                   (id)[UIColor whiteColor].CGColor,
                                   (id)[UIColor purpleColor].CGColor, nil];
        CGFloat gradientLocations[] = {0, 1};
        
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
                                                            (__bridge CFArrayRef)gradientColors,
                                                            gradientLocations);
        
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSaveGState(context);
        CGContextDrawLinearGradient(context, gradient, startPoint, endPoint,0);
        CGContextRestoreGState(context);
        CGGradientRelease(gradient);
        CGColorSpaceRelease(colorSpace);
    }
    

    效果如下:

    你也可以用一个自定义的形状来抱住你创建的渐变,如下所示:

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        NSArray* gradientColors = [NSArray arrayWithObjects:
                                   (id)[UIColor whiteColor].CGColor,
                                   (id)[UIColor purpleColor].CGColor, nil];
        CGFloat gradientLocations[] = {0, 1};
        
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
                                                            (__bridge CFArrayRef)gradientColors,
                                                            gradientLocations);
        
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSaveGState(context);
        CGContextMoveToPoint(context, 100, 100);
        CGContextAddArc(context, 100, 100, 60, 1.04 , 2.09 , 0);
        CGContextClosePath(context);
        CGContextClip(context);
        
        
        CGPoint endshine;
        CGPoint startshine;
        startshine = CGPointMake(100 + 60 * cosf( 1.57 ),100+ 60 * sinf( 1.57 ));
        endshine = CGPointMake(100,100);
        CGContextDrawLinearGradient(context,gradient , startshine, endshine, kCGGradientDrawsAfterEndLocation);
        CGContextRestoreGState(context);
    

    效果如下:

    上面除了使用drawdrawRadialGradientWithRect函数外,还可以使用

    CGGradientCreateWithColorComponents包含以下4个参数:

    Color Space:和上面一样
    颜色分量的数组:这个数组必须包含CGFloat类型的红、绿、蓝和alpha值。数组中元素的数量和接下来两个参数密切。从本质来讲,你必须让这个数组包含足够的值,用来指定第四个参数中位置的数量。所以如果你需要两个位置(起点和终点),那么你必须为数组提供两种颜色。
    位置数组:颜色数组中各个颜色的位置,此参数控制该渐变从一种颜色过渡到另一种颜色的速度有多快。
    位置的数量:这个参数指明了我们需要多少颜色和位置。

    例如:

    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, (CGFloat[]){  
            0.8, 0.2, 0.2, 1.0,  
            0.2, 0.8, 0.2, 1.0,  
            0.2, 0.2, 0.8, 1.0  
        }, (CGFloat[]){  
            0.0, 0.5, 1.0  
        }, 3);
    

    3.一些可能需要注意的地方

    上面我们将了自定义绘图,相对与它来讲,UIView及其子类是高度优化的,所以在能用UIView解决的地方,尽量不要使用自定义绘图,最快的绘图方式就是根本不绘制(废话=_=),iOS在尽量避免调用drawRect:方法,使用一个合适的contentMode方法,系统在旋转或重新调整大小时就不需要调用drawRect:方法,导致drawRect:方法运行的最常见情况是调用了setNeedDisplay。

    你可以在这里下载完整的代码。如果你觉得对你有帮助,希望你不吝啬你的star:)

    相关文章

      网友评论

      本文标题:绘图

      本文链接:https://www.haomeiwen.com/subject/cwqbxttx.html