繪圖 UIBezierPath

視圖可以通過子視圖、圖層或實現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:)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章