UIBezierPath詳解
我在寫本篇文章之前,也沒有系統(tǒng)學(xué)習(xí)過貝塞爾曲線,只是曾經(jīng)某一次的需求需要使用到,才臨時百度看了一看而且使用最基本的功能。現(xiàn)在總算有時間停下來好好研究研究這個神奇而偉大的貝塞爾先生!
使用UIBezierPath可以創(chuàng)建基于矢量的路徑,此類是Core Graphics框架關(guān)于路徑的封裝。使用此類可以定義簡單的形狀,如橢圓、矩形或者有多個直線和曲線段組成的形狀等。
UIBezierPath是CGPathRef數(shù)據(jù)類型的封裝。如果是基于矢量形狀的路徑,都用直線和曲線去創(chuàng)建。我們使用直線段去創(chuàng)建矩形和多邊形,使用曲線去創(chuàng)建圓弧(arc)、圓或者其他復(fù)雜的曲線形狀。
使用UIBezierPath畫圖步驟:
創(chuàng)建一個UIBezierPath對象
調(diào)用-moveToPoint:設(shè)置初始線段的起點(diǎn)
添加線或者曲線去定義一個或者多個子路徑
改變UIBezierPath對象跟繪圖相關(guān)的屬性。如,我們可以設(shè)置畫筆的屬性、填充樣式等
我們先看看UIBezierPath類提供了哪些創(chuàng)建方式,這些都是工廠方法,直接使用即可。
+ (instancetype)bezierPath;
+ (instancetype)bezierPathWithRect:(CGRect)rect;
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect
cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect
byRoundingCorners:(UIRectCorner)corners
cornerRadii:(CGSize)cornerRadii;
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center
radius:(CGFloat)radius
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
clockwise:(BOOL)clockwise;
+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;
下面我們一個一個地介紹其用途。
+ (instancetype)bezierPath;
這個使用比較多,因?yàn)檫@個工廠方法創(chuàng)建的對象,我們可以根據(jù)我們的需要任意定制樣式,可以畫任何我們想畫的圖形。
+ (instancetype)bezierPathWithRect:(CGRect)rect;
這個工廠方法根據(jù)一個矩形畫貝塞爾曲線。
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
這個工廠方法根據(jù)一個矩形畫內(nèi)切曲線。通常用它來畫圓或者橢圓。
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect
cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect
byRoundingCorners:(UIRectCorner)corners
cornerRadii:(CGSize)cornerRadii;
第一個工廠方法是畫矩形,但是這個矩形是可以畫圓角的。第一個參數(shù)是矩形,第二個參數(shù)是圓角大小。
第二個工廠方法功能是一樣的,但是可以指定某一個角畫成圓角。像這種我們就可以很容易地給UIView擴(kuò)展添加圓角的方法了。
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center
radius:(CGFloat)radius
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
clockwise:(BOOL)clockwise;
這個工廠方法用于畫弧,參數(shù)說明如下:
center: 弧線中心點(diǎn)的坐標(biāo)
radius: 弧線所在圓的半徑
startAngle: 弧線開始的角度值
endAngle: 弧線結(jié)束的角度值
clockwise: 是否順時針畫弧線
溫馨提示:我們下面的代碼都是在自定義的BezierPathView類中的- (void)drawRect:(CGRect)rect方法中調(diào)用
先看效果圖:
// 畫三角形
- (void)drawTrianglePath {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 20)];
[path addLineToPoint:CGPointMake(self.frame.size.width - 40, 20)];
[path addLineToPoint:CGPointMake(self.frame.size.width / 2, self.frame.size.height - 20)];
// 最后的閉合線是可以通過調(diào)用closePath方法來自動生成的,也可以調(diào)用-addLineToPoint:方法來添加
//? [path addLineToPoint:CGPointMake(20, 20)];
[path closePath];
// 設(shè)置線寬
path.lineWidth = 1.5;
// 設(shè)置填充顏色
UIColor *fillColor = [UIColor greenColor];
[fillColor set];
[path fill];
// 設(shè)置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];
[strokeColor set];
// 根據(jù)我們設(shè)置的各個點(diǎn)連線
[path stroke];
}
我們設(shè)置畫筆顏色通過set方法:
UIColor *strokeColor = [UIColor blueColor];
[strokeColor set];
如果我們需要設(shè)置填充顏色,比如這里設(shè)置為綠色,那么我們需要在設(shè)置畫筆顏色之前先設(shè)置填充顏色,否則畫筆顏色就被填充顏色替代了。也就是說,如果要讓填充顏色與畫筆顏色不一樣,那么我們的順序必須是先設(shè)置填充顏色再設(shè)置畫筆顏色。如下,這兩者順序不能改變。因?yàn)槲覀冊O(shè)置填充顏色也是跟設(shè)置畫筆顏色一樣調(diào)用UIColor的-set方法。
// 設(shè)置填充顏色
UIColor *fillColor = [UIColor greenColor];
[fillColor set];
[path fill];
// 設(shè)置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];
[strokeColor set];
先看效果圖:
// 畫矩形
- (void)drawRectPath {
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(20, 20, self.frame.size.width - 40, self.frame.size.height - 40)];
path.lineWidth = 1.5;
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineJoinBevel;
// 設(shè)置填充顏色
UIColor *fillColor = [UIColor greenColor];
[fillColor set];
[path fill];
// 設(shè)置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];
[strokeColor set];
// 根據(jù)我們設(shè)置的各個點(diǎn)連線
[path stroke];
}
lineCapStyle屬性是用來設(shè)置線條拐角帽的樣式的,其中有三個選擇:
/* Line cap styles. */
typedef CF_ENUM(int32_t, CGLineCap) {
kCGLineCapButt,
kCGLineCapRound,
kCGLineCapSquare
};
其中,第一個是默認(rèn)的,第二個是輕微圓角,第三個正方形。
lineJoinStyle屬性是用來設(shè)置兩條線連結(jié)點(diǎn)的樣式,其中也有三個選擇:
/* Line join styles. */
typedef CF_ENUM(int32_t, CGLineJoin) {
kCGLineJoinMiter,
kCGLineJoinRound,
kCGLineJoinBevel
};
其中,第一個是默認(rèn)的表示斜接,第二個是圓滑銜接,第三個是斜角連接。
我們可以使用+ bezierPathWithOvalInRect:方法來畫圓,當(dāng)我們傳的rect參數(shù)是一下正方形時,畫出來的就是圓。
先看效果圖:
- (void)drawCiclePath {
// 傳的是正方形,因此就可以繪制出圓了
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, self.frame.size.width - 40, self.frame.size.width - 40)];
// 設(shè)置填充顏色
UIColor *fillColor = [UIColor greenColor];
[fillColor set];
[path fill];
// 設(shè)置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];
[strokeColor set];
// 根據(jù)我們設(shè)置的各個點(diǎn)連線
[path stroke];
}
注意:要畫圓,我們需要傳的rect參數(shù)必須是正方形哦!
先看效果圖:
前面我們已經(jīng)畫圓了,我們可以使用+ bezierPathWithOvalInRect:方法來畫圓,當(dāng)我們傳的rect參數(shù)是一下正方形時,畫出來的就是圓。那么我們要是不傳正方形,那么繪制出來的就是橢圓了。
// 畫橢圓
- (void)drawOvalPath {
// 傳的是不是正方形,因此就可以繪制出橢圓圓了
UIBezierPathpath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, self.frame.size.width - 80, self.frame.size.height - 40)];
// 設(shè)置填充顏色
UIColorfillColor = [UIColor greenColor];
[fillColor set];
[path fill];
// 設(shè)置畫筆顏色
UIColorstrokeColor = [UIColor blueColor];
[strokeColor set];
// 根據(jù)我們設(shè)置的各個點(diǎn)連線
[path stroke];
}
##### 畫帶圓角的矩形
**
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect
cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect
byRoundingCorners:(UIRectCorner)corners
cornerRadii:(CGSize)cornerRadii;
第一個工廠方法是畫矩形,但是這個矩形是可以畫圓角的。第一個參數(shù)是矩形,第二個參數(shù)是圓角大小。
第二個工廠方法功能是一樣的,但是可以指定某一個角畫成圓角。像這種我們就可以很容易地給UIView擴(kuò)展添加圓角的方法了。
四個都是圓角10:
- (void)drawRoundedRectPath {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 20, self.frame.size.width - 40, self.frame.size.height - 40) cornerRadius:10];
// 設(shè)置填充顏色
UIColor *fillColor = [UIColor greenColor];
[fillColor set];
[path fill];
// 設(shè)置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];
[strokeColor set];
// 根據(jù)我們設(shè)置的各個點(diǎn)連線
[path stroke];
}
如果要畫只有一個角是圓角,那么我們就修改創(chuàng)建方法:
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 20, self.frame.size.width - 40, self.frame.size.height - 40) byRoundingCorners:UIRectCornerTopRight cornerRadii:CGSizeMake(20, 20)];
其中第一個參數(shù)一樣是傳了個矩形,第二個參數(shù)是指定在哪個方向畫圓角,第三個參數(shù)是一個CGSize類型,用來指定水平和垂直方向的半徑的大小。看下效果圖:
畫弧前,我們需要了解其參考系,如下圖(圖片來自網(wǎng)絡(luò)):
#define? kDegreesToRadians(degrees)? ((pi * degrees)/ 180)
- (void)drawARCPath {
const CGFloat pi = 3.14159265359;
CGPoint center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:100
startAngle:0
endAngle:kDegreesToRadians(135)
clockwise:YES];
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineJoinRound;
path.lineWidth = 5.0;
UIColor *strokeColor = [UIColor redColor];
[strokeColor set];
[path stroke];
}
效果圖如下:
我們要明確一點(diǎn),畫弧參數(shù)startAngle和endAngle使用的是弧度,而不是角度,因此我們需要將常用的角度轉(zhuǎn)換成弧度。對于效果圖中,我們設(shè)置弧的中心為控件的中心,起點(diǎn)弧度為0,也就是正東方向,而終點(diǎn)是135度角的位置。如果設(shè)置的clockwise:YES是逆時針方向繪制,如果設(shè)置為NO,效果如下:
這兩者正好是相反的。
先來學(xué)習(xí)一下關(guān)于控制點(diǎn),如下圖(圖片來自網(wǎng)絡(luò)):
畫二次貝塞爾曲線,是通過調(diào)用此方法來實(shí)現(xiàn)的:
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint
參數(shù)說明:
endPoint:終端點(diǎn)
controlPoint:控制點(diǎn),對于二次貝塞爾曲線,只有一個控制點(diǎn)
看效果圖:
- (void)drawSecondBezierPath {
UIBezierPath *path = [UIBezierPath bezierPath];
// 首先設(shè)置一個起始點(diǎn)
[path moveToPoint:CGPointMake(20, self.frame.size.height - 100)];
// 添加二次曲線
[path addQuadCurveToPoint:CGPointMake(self.frame.size.width - 20, self.frame.size.height - 100)
controlPoint:CGPointMake(self.frame.size.width / 2, 0)];
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineJoinRound;
path.lineWidth = 5.0;
UIColor *strokeColor = [UIColor redColor];
[strokeColor set];
[path stroke];
}
畫二次貝塞爾曲線的步驟:
先設(shè)置一個起始點(diǎn),也就是通過-moveToPoint:設(shè)置
調(diào)用-addQuadCurveToPoint:controlPoint:方法設(shè)置終端點(diǎn)和控制點(diǎn),以畫二次曲線
在效果圖中,拱橋左邊的起始點(diǎn)就是我們設(shè)置的起始點(diǎn),最右邊的終點(diǎn),就是我們設(shè)置的終端點(diǎn),而我們設(shè)置的控制點(diǎn)為(width / 2, 0)對應(yīng)于紅色矩形中水平方向在正中央,而垂直方向在最頂部。
這個樣式看起來很像sin或者cos函數(shù)吧?這兩個只是特例而已,其實(shí)可以畫任意圖形,只是想不到,沒有做不到的。
貝塞爾曲線必定通過首尾兩個點(diǎn),稱為端點(diǎn);中間兩個點(diǎn)雖然未必要通過,但卻起到牽制曲線形狀路徑的作用,稱作控制點(diǎn)。關(guān)于三次貝塞爾曲線的控制器,看下圖:
提示:其組成是起始端點(diǎn)+控制點(diǎn)1+控制點(diǎn)2+終止端點(diǎn)
如下方法就是畫三次貝塞爾曲線的關(guān)鍵方法,以三個點(diǎn)畫一段曲線,一般和-moveToPoint:配合使用。
- (void)addCurveToPoint:(CGPoint)endPoint
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2
看下效果圖:
實(shí)現(xiàn)代碼是這樣的:
- (void)drawThirdBezierPath {
UIBezierPath *path = [UIBezierPath bezierPath];
// 設(shè)置起始端點(diǎn)
[path moveToPoint:CGPointMake(20, 150)];
[path addCurveToPoint:CGPointMake(300, 150)
controlPoint1:CGPointMake(160, 0)
controlPoint2:CGPointMake(160, 250)];
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineJoinRound;
path.lineWidth = 5.0;
UIColor *strokeColor = [UIColor redColor];
[strokeColor set];
[path stroke];
}
我們需要注意,這里確定的起始端點(diǎn)為(20,150),終止端點(diǎn)為(300, 150),基水平方向是一致的。控制點(diǎn)1的坐標(biāo)是(160,0),水平方向相當(dāng)于在中間附近,這個參數(shù)可以調(diào)整。控制點(diǎn)2的坐標(biāo)是(160,250),如果以兩個端點(diǎn)的連線為水平線,那么就是250-150=100,也就是在水平線下100。這樣看起來就像一個sin函數(shù)了。