視圖可以通過子視圖、圖層或?qū)崿F(xiàn)drawRect:方法來表現(xiàn)內(nèi)容,如果說實現(xiàn)了drawRect:方法,那么最好就不要混用其他方法了,如圖層和子視圖。自定義繪圖大部分是由UIKit或者Core Graphics來實現(xiàn)的。現(xiàn)在我們來講講UIBezierPath和Core Graphics。
1.UIBezierPath
UIKit中的UIBezierPath是Core Graphics框架關(guān)于path的一個封裝。可以創(chuàng)建基于矢量的路徑,例如橢圓或者矩形,或者有多個直線和曲線段組成的形狀。
1.繪制矩形
繪制矩形最簡單的辦法是使用UIRectFrame和UIRectFill,如下
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor]setFill];
UIRectFill(CGRectMake(20, 20, 100, 50));
}
其中UIColor setFill是設(shè)置畫筆顏色。
通過使用UIBezierPath可以自定義繪制線條的粗細,是否圓角等。
- (void)drawRect:(CGRect)rect
{
UIColor *color = [UIColor colorWithRed:0 green:0 blue:0.7 alpha:1];
[color set]; //設(shè)置線條顏色
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:方法去構(gòu)建。moveToPoint:設(shè)置我們想要創(chuàng)建形狀的起點。從這點開始,我們可以用方法addLineToPoint:去創(chuàng)建一個形狀的線段。
我們可以連續(xù)的創(chuàng)建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];//第五條線通過調(diào)用closePath方法得到的
//根據(jù)坐標點連線
[aPath stroke];
[aPath fill];
}
效果如下:
4.不規(guī)則形狀
想畫弧線組成的不規(guī)則形狀,我們需要使用中心點、弧度和半徑,如下圖。弧度使用順時針腳底,0弧度指向右邊,pi/2指向下方,pi指向左邊,-pi/2指向上方。然后使用bezierPathWithArcCenter: radius: startAngle endAngle: clockwise:方法來繪制。
1).繪制一段弧度
- (void)drawRect:(CGRect)rect
{
UIColor *color = [UIColor redColor];
[color set]; //設(shè)置線條顏色
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];
}
結(jié)果如下:
2).兩種貝塞爾曲線
a).第一種:
- (void)drawRect:(CGRect)rect
{
UIColor *color = [UIColor redColor];
[color set]; //設(shè)置線條顏色
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]; //設(shè)置線條顏色
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:四舍五入函數(shù)
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];
}
記得調(diào)用以下這個方法,使其view變化后(例如橫屏了)重新調(diào)用drawRect:
- (void)awakeFromNib {
// Comment this line to see default behavior
self.contentMode = UIViewContentModeRedraw;
}
2.CoreGraphics
上面我們講過,UIBezierPath是CoreGraphics的封裝,使用它可以完成大部分的繪圖操作,不過更底層的CoreGraphics更加強大。
CoreGraphics,也稱為Quartz 2D 是UIKit下的主要繪圖系統(tǒng),頻繁的用于繪制自定義視圖。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics數(shù)據(jù)結(jié)構(gòu)和函數(shù)可以通過前綴CG來識別。
由于像素是依賴于目標的,所以2D繪圖并不能操作單獨的像素,我們可以從上下文(Context)讀取它。所以我們在繪制之前需要通過
CGContextRef ctx = UIGraphicsGetCurrentContext()
獲取當前推入堆棧的圖形,相當于你所要繪制圖形的圖紙,然后繪圖就好比在畫布上拿著畫筆機械的進行畫畫,通過制定不同的參數(shù)來進行不同的繪制。
畫完之后我們需要通過
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)
其中的參數(shù)說明如下:
c 當前圖形
x 圓弧的中心點坐標x
y 曲線控制點的y坐標
radius 指定點的x坐標值
startAngle 弧的起點與正X軸的夾角,
endAngle 弧的終點與正X軸的夾角
clockwise 指定1創(chuàng)建一個順時針的圓弧,或是指定0創(chuàng)建一個逆時針圓弧
所以我們可以通過下面創(chuàng)建圓形:
- (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);
}
現(xiàn)在看起來:
繪制橢圓我們需要先給定一個容納橢圓的矩形,然后使用
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);
}
現(xiàn)在看起來:
3.多邊形
繪制多邊形需要通過CGContextMoveToPoint從一個開始點開始一個新的子路徑,然后通過CGContextAddLineToPoint在當前點追加直線段,最后通過CGContextClosePath關(guān)閉路徑即可。如下我們繪制一個三角形:
- (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);
}
現(xiàn)在看起來:
4.不規(guī)則形狀
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);
}
我們畫兩個如上的曲線,現(xiàn)在看起來:
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);
}
現(xiàn)在看起來:
還不錯。
5.加陰影效果
可以通過
CGContextSetShadowWithColor(CGContextRef context, CGSize offset, CGFloat blur, CGColorRef color)
設(shè)置陰影效果,4個參數(shù)分別是圖形上下文,偏移量(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
放射式漸變以某種顏色從一點開始,以另一種顏色在其它點結(jié)束。它看起來會是一個圓。
為了創(chuàng)建一個放射式漸變,你要調(diào)用CGGradientCreateWithColors函數(shù)。這個函數(shù)的返回值是一個新的類型為CGGradientRef的漸變。
CGGradientCreateWithColors包含以下3個參數(shù):
Color Space:這是一個色彩范圍的容器,類型是CGColorSpaceRef. 這個參數(shù),我們可以傳入CGColorSpaceCreateDeviceRGB函數(shù)的返回值,它將給我們一個RGB色彩空間。
顏色分量的數(shù)組:這個數(shù)組必須包含顏色的數(shù)組值。
位置數(shù)組:顏色數(shù)組中各個顏色的位置,此參數(shù)控制該漸變從一種顏色過渡到另一種顏色的速度有多快。
如下:
- (void)drawdrawRadialGradientWithRect:(CGRect)rect
{
//先創(chuàng)造一個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));
調(diào)用完上面那個函數(shù)后,我們需要使用:
CGContextDrawRadialGradient(CGContextRef context, CGGradientRef gradient, CGPoint startCenter, CGFloat startRadius, CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options)
進行上下文繪制,參數(shù)說明如下:
CGPoint startCenter:白色的起點(中心圓點)
CGFloat startRadius:起點的半徑,這個值多大,中心就是多大一塊純色的白圈
CGPoint endCenter:白色的終點, 可以和起點一樣,不一樣的話就像探照燈一樣從起點投影到這個終點
CGFloat endRadius:終點的半徑,
如下:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawRadialGradient(context, gradient,
startCenter, 0,
startCenter, radius,
0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
我們這樣調(diào)用它:
[self drawdrawRadialGradientWithRect:CGRectMake(120, 510, 60, 60)];
效果如下;
2)線性漸變:CGGradientCreateWithColorComponents
線性漸變以某種顏色從一點開始,以另一種顏色在其它點結(jié)束。
你先要調(diào)用上面講到的drawdrawRadialGradientWithRect 函數(shù)去創(chuàng)建一個gradient漸變,創(chuàng)建好gradient后,我們將使用
CGContextDrawLinearGradient(CGContextRef context, CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions options)
在圖形上下文中繪制,此過程需要五個參數(shù), 比上面的輻射漸變多了最后一個參數(shù):
Gradient drawing options :指定當你的起點或者終點不在圖形上下文的邊緣內(nèi)時該如何處理。你可以使用你的開始或結(jié)束顏色來填充漸變以外的空間。此參數(shù)為以下值之一:
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);
}
效果如下:
你也可以用一個自定義的形狀來抱住你創(chuàng)建的漸變,如下所示:
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函數(shù)外,還可以使用
CGGradientCreateWithColorComponents包含以下4個參數(shù):
Color Space:和上面一樣
顏色分量的數(shù)組:這個數(shù)組必須包含CGFloat類型的紅、綠、藍和alpha值。數(shù)組中元素的數(shù)量和接下來兩個參數(shù)密切。從本質(zhì)來講,你必須讓這個數(shù)組包含足夠的值,用來指定第四個參數(shù)中位置的數(shù)量。所以如果你需要兩個位置(起點和終點),那么你必須為數(shù)組提供兩種顏色。
位置數(shù)組:顏色數(shù)組中各個顏色的位置,此參數(shù)控制該漸變從一種顏色過渡到另一種顏色的速度有多快。
位置的數(shù)量:這個參數(shù)指明了我們需要多少顏色和位置。
例如:
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及其子類是高度優(yōu)化的,所以在能用UIView解決的地方,盡量不要使用自定義繪圖,最快的繪圖方式就是根本不繪制(廢話=_=),iOS在盡量避免調(diào)用drawRect:方法,使用一個合適的contentMode方法,系統(tǒng)在旋轉(zhuǎn)或重新調(diào)整大小時就不需要調(diào)用drawRect:方法,導致drawRect:方法運行的最常見情況是調(diào)用了setNeedDisplay。
你可以在這里下載完整的代碼。如果你覺得對你有幫助,希望你不吝嗇你的star:)