iOS動畫 —— UIBezierPath

簡介

UIBezierPath類創(chuàng)建基于矢量的路徑,例如橢圓或者矩形,或者有多個直線和曲線段組成的形狀。

UIBezierPath是UIKit中的一個關(guān)于圖形繪制的類,是通過Quartz 2D也就是CG(Core Graphics)CGPathRef的封裝得到的,從高級特性支持來看不及CG。

使用UIBezierPath,你只能在當(dāng)前圖形上下文中繪制。 CGContextRef即圖形上下文。
1.重寫UIView的drawRect方法,在該方法里便可得到context;
2.調(diào)用UIGraphicsBeginImageContextWithOptions方法得到

drawRect: 觸發(fā)觸發(fā)時機
1、當(dāng)view第一次顯示到屏幕上時;
2、當(dāng)調(diào)用view的setNeedsDisplay或者setNeedsDisplayInRect:方法時。

繪制流程

  1. 初始化一個 UIBezierPath 對象
  2. 設(shè)置相關(guān)的屬性;
  3. 調(diào)用 -moveToPoint: 方法初始線段的起點;
  4. 添加線段或者曲線段去構(gòu)建一個或者多個子路徑;
- (void)drawRect:(CGRect)rect // 重寫drawRect方法
{
    // 1.初始化圖形相應(yīng)的UIBezierPath對象
    UIBezierPath* aPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 200, 200)]; // 2.
    // 2.設(shè)置一些修飾屬性 
    aPath.lineWidth = 8.0;
    //路徑的終點形狀,
    aPath.lineCapStyle = kCGLineCapRound; 
    //路徑的連接點形狀
    aPath.lineJoinStyle = kCGLineCapRound; 
    UIColor *color = [UIColor colorWithRed:0 green:0 blue:0.7 alpha:1];
    [color set]; 

    // 3.起點
    [aPath moveToPoint:CGPointMake(20, 20)];

    // 4.繪制線條
    [aPath addLineToPoint:CGPointMake(100, 100)];

    [aPath stroke]; // 渲染,完成繪制
}

創(chuàng)建 UIBezierPath

+ (instancetype) bezierPath;
/**
  * 該方法將會創(chuàng)建一個閉合路徑, 起始點是 rect 參數(shù)的的 origin, 并且按照順時針方向添加直線, 最終形成矩形
  * @param rect:   矩形路徑的 Frame
  */
+ (instancetype)bezierPathWithRect:(CGRect)rect;

/**
  * 該方法將會創(chuàng)建一個閉合路徑,  該方法會通過順時針的繪制貝塞爾曲線, 繪制出一個近似橢圓的形狀. 如果 rect 參數(shù)指定了一個矩形, 那么該 UIBezierPath 對象將會描述一個圓形.
  * @param rect:   矩形路徑的 Frame
  */
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
/**
  * 該方法將會創(chuàng)建一個閉合路徑,  該方法會順時針方向連續(xù)繪制直線和曲線.  當(dāng) rect 為正方形時且 cornerRadius 等于邊長一半時, 則該方法會描述一個圓形路徑.
  * @param rect:   矩形路徑的 Frame
  * @param cornerRadius:   矩形的圓角半徑
  */
+ (instancetype) bezierPathWithRoundedRect:(CGRect)rect 
                              cornerRadius:(CGFloat)cornerRadius;
/**
  * 該方法將會創(chuàng)建一個閉合路徑,  該方法會順時針方向連續(xù)繪制直線和曲線.  
  * @param rect:   矩形路徑的 Frame
  * @param corners:   UIRectCorner 枚舉類型, 指定矩形的哪個角變?yōu)閳A角
  * @param cornerRadii:   矩形的圓角半徑
  */
+ (instancetype) bezierPathWithRoundedRect:(CGRect)rect 
                         byRoundingCorners:(UIRectCorner)corners
                               cornerRadii:(CGSize)cornerRadii;

/**
  * 該方法會創(chuàng)建出一個開放路徑, 創(chuàng)建出來的圓弧是圓的一部分. 在默認(rèn)的坐標(biāo)系統(tǒng)中, 開始角度 和 結(jié)束角度 都是基于單位圓的(看下面這張圖). 調(diào)用這個方法之后, currentPoint 將會設(shè)置為圓弧的結(jié)束點.
  * 舉例來說: 指定其實角度為0, 指定結(jié)束角度為π, 設(shè)置 clockwise 屬性為 YES, 將會繪制出圓的下半部分.
  * 然而當(dāng)我們不修改起始角度 和 結(jié)束角度, 我們僅僅將 clockwise 角度設(shè)置為 NO, 則會繪制出來一個圓的上半部分.
  * @param center:   圓心
  * @param radius: 半徑
  * @param startAngle:   起始角度
  * @param endAngle:   結(jié)束角度
  * @param clockwise:   是否順時針繪制
  */
+ (instancetype) bezierPathWithArcCenter:(CGPoint)center 
                                  radius:(CGFloat)radius 
                              startAngle:(CGFloat)startAngle 
                                endAngle:(CGFloat)endAngle 
                               clockwise:(BOOL)clockwise;
+ (instancetype) bezierPathWithCGPath:(CGPathRef)CGPath;

/**
  * 通過該方法反轉(zhuǎn)一條路徑, 并不會修改該路徑的樣子. 它僅僅是修改了繪制的方向
  * @return: 返回一個新的 UIBezierPath 對象, 形狀和原來路徑的形狀一樣,
  *          但是繪制的方向相反.
  */
- (UIBezierPath *) bezierPathByReversingPath;

構(gòu)造路徑

/**
  * 如果當(dāng)前有正在繪制的子路徑, 該方法則會隱式的結(jié)束當(dāng)前路徑, 
  * 并將 currentPoint 設(shè)置為指定點. 當(dāng)上一條子路徑被終止, 該方法
  * 實際上并不會去閉合上一條子路徑. 所以上一條自路徑的起始點 和
  * 結(jié)束點并沒有被鏈接.
  * 對于大多數(shù)構(gòu)造路徑相關(guān)的方法而言, 在你繪制直線或曲線之前, 需要先調(diào)用這個方法.
  * @param point:   當(dāng)前坐標(biāo)系統(tǒng)中的某一點
  */
- (void)moveToPoint:(CGPoint)point;

/**
  * 該方法將會從 currentPoint 到 指定點 鏈接一條直線. 
  * Note: 在追加完這條直線后, 該方法將會更新 currentPoint 為 指定點
  *       調(diào)用該方法之前, 你必須先設(shè)置 currentPoint. 如果當(dāng)前繪制路徑
  *       為空, 并且未設(shè)置 currentPoint, 那么調(diào)用該方法將不會產(chǎn)生任何
  *       效果.
  * @param point:   繪制直線的終點坐標(biāo), 當(dāng)前坐標(biāo)系統(tǒng)中的某一點
  */
- (void)addLineToPoint:(CGPoint)point;

/**
  * 該方法將會從 currentPoint 添加一條指定的圓弧.
  * 該方法的介紹和構(gòu)造方法中的一樣. 請前往上文查看
  * @param center: 圓心
  * @param radius: 半徑
  * @param startAngle: 起始角度
  * @param endAngle: 結(jié)束角度
  * @param clockwise: 是否順時針繪制
  */
- (void)addArcWithCenter:(CGPoint)center 
                  radius:(CGFloat)radius 
              startAngle:(CGFloat)startAngle 
                endAngle:(CGFloat)endAngle 
               clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);

/**
  * 該方法將會從 currentPoint 到 指定的 endPoint 追加一條二次貝塞爾曲線.
  * currentPoint、endPoint、controlPoint 三者的關(guān)系最終定義了二次貝塞爾曲線的形狀.
  * 二次貝塞爾曲線的彎曲由一個控制點來控制. 如下圖所示
  * Note: 調(diào)用該方法前, 你必須先設(shè)置 currentPoint, 如果路徑為空, 
  *       并且尚未設(shè)置 currentPoint, 調(diào)用該方法則不會產(chǎn)生任何效果. 
  *       當(dāng)添加完貝塞爾曲線后, 該方法將會自動更新 currentPoint 為
  *       指定的結(jié)束點
  * @param endPoint: 終點
  * @param controlPoint: 控制點
  */
- (void)addQuadCurveToPoint:(CGPoint)endPoint 
               controlPoint:(CGPoint)controlPoint;

/**
  * 該方法將會從 currentPoint 到 指定的 endPoint 追加一條三次貝塞爾曲線.
  * 三次貝塞爾曲線的彎曲由兩個控制點來控制. 如下圖所示
  * Note: 調(diào)用該方法前, 你必須先設(shè)置 currentPoint, 如果路徑為空, 
  *       并且尚未設(shè)置 currentPoint, 調(diào)用該方法則不會產(chǎn)生任何效果. 
  *       當(dāng)添加完貝塞爾曲線后, 該方法將會自動更新 currentPoint 為
  *       指定的結(jié)束點
  * @param endPoint: 終點
  * @param controlPoint1: 控制點1
  * @param controlPoint2: 控制點2
  */
- (void)addCurveToPoint:(CGPoint)endPoint 
          controlPoint1:(CGPoint)controlPoint1 
          controlPoint2:(CGPoint)controlPoint2;
/**
  * 該方法將會從 currentPoint 到子路經(jīng)的起點 繪制一條直線, 
  * 以此來關(guān)閉當(dāng)前的自路徑. 緊接著該方法將會更新 currentPoint
  * 為 剛添加的這條直線的終點, 也就是當(dāng)前子路經(jīng)的起點.
  */
- (void)closePath;

- (void)removeAllPoints;

/**
  * 該方法將會在當(dāng)前 UIBezierPath 對象的路徑中追加
  * 指定的 UIBezierPath 對象中的內(nèi)容. 
  */
- (void)appendPath:(UIBezierPath *)bezierPath;

/**
  * 獲取這個屬性, 你將會獲得一個不可變的 CGPathRef 對象,
  * 他可以傳入 CoreGraphics 提供的函數(shù)中
  * 你可以是用 CoreGraphics 框架提供的方法創(chuàng)建一個路徑, 
  * 并給這個屬性賦值, 當(dāng)時設(shè)置了一個新的路徑后, 
  * 這個將會對你給出的路徑對象進行 Copy 操作
  */
@property(nonatomic) CGPathRef CGPath;

/**
  * 該屬性的值, 將會是下一條繪制的直線或曲線的起始點.
  * 如果當(dāng)前路徑為空, 那么該屬性的值將會是 CGPointZero
  */
@property(nonatomic, readonly) CGPoint currentPoint;

繪圖屬性

/**
  * 線寬屬性定義了 `UIBezierPath` 對象中繪制的曲線規(guī)格. 默認(rèn)為: 1.0
  */
@property(nonatomic) CGFloat lineWidth;

/**
  * 該屬性應(yīng)用于曲線的終點和起點. 該屬性在一個閉合子路經(jīng)中是無效果的. 默認(rèn)為: kCGLineCapButt
  */
@property(nonatomic) CGLineCap lineCapStyle;

// CGPath.h
/* Line cap styles. */
typedef CF_ENUM(int32_t, CGLineCap) {
    kCGLineCapButt,
    kCGLineCapRound,
    kCGLineCapSquare
};
/**
  * 默認(rèn)為: kCGLineJoinMiter.
  */
@property(nonatomic) CGLineJoin lineJoinStyle;

// CGPath.h
/* Line join styles. */
typedef CF_ENUM(int32_t, CGLineJoin) {
    kCGLineJoinMiter,
    kCGLineJoinRound,
    kCGLineJoinBevel
};
/**
  * 兩條線交匯處內(nèi)角和外角之間的最大距離, 只有當(dāng)連接點樣式為 kCGLineJoinMiter
  * 時才會生效,最大限制為10
  * 我們都知道, 兩條直線相交時, 夾角越小, 斜接長度就越大.
  * 該屬性就是用來控制最大斜接長度的.
  * 當(dāng)我們設(shè)置了該屬性, 如果斜接長度超過我們設(shè)置的范圍, 
  * 則連接處將會以 kCGLineJoinBevel 連接類型進行顯示.
  */
@property(nonatomic) CGFloat miterLimit;
2452150-d6647c67c61e87c6.png
  • 渲染精度
/**
  * 該屬性用來確定渲染曲線路徑的精確度.
  * 該屬性的值用來測量真實曲線的點和渲染曲線的點的最大允許距離.
  * 值越小, 渲染精度越高, 會產(chǎn)生相對更平滑的曲線, 但是需要花費更
  * 多的計算時間. 值越大導(dǎo)致則會降低渲染精度, 這會使得渲染的更迅
  * 速. flatness 的默認(rèn)值為 0.6.
  * Note: 大多數(shù)情況下, 我們都不需要修改這個屬性的值. 然而當(dāng)我們
  *       希望以最小的消耗去繪制一個臨時的曲線時, 我們也許會臨時增
  *       大這個值, 來獲得更快的渲染速度.
  */

@property(nonatomic) CGFloat flatness;

/**
  * 設(shè)置為 YES, 則路徑將會使用 基偶規(guī)則 (even-odd) 進行填充.
  * 設(shè)置為 NO,  則路徑將會使用 非零規(guī)則 (non-zero) 規(guī)則進行填充.
  */
@property(nonatomic) BOOL usesEvenOddFillRule;

/**
  * @param pattern: 該屬性是一個 C 語言的數(shù)組, 其中每一個元素都是 CGFloat
  *                 數(shù)組中的元素代表著線段每一部分的長度, 第一個元素代表線段的第一條線,
  *                 第二個元素代表線段中的第一個間隙. 這個數(shù)組中的值是輪流的. 來解釋一下
  *                 什么叫輪流的. 
  *                 舉個例子: 聲明一個數(shù)組 CGFloat dash[] = @{3.0, 1.0}; 
  *                 這意味著繪制的虛線的第一部分長度為3.0, 第一個間隙長度為1.0, 虛線的
  *                 第二部分長度為3.0, 第二個間隙長度為1.0\. 以此類推.

  * @param count: 這個參數(shù)是 pattern 數(shù)組的個數(shù)
  * @param phase: 這個參數(shù)代表著, 虛線從哪里開始繪制.
  *                 舉個例子: 這是 phase 為 6\. pattern[] = @{5, 2, 3, 2}; 那么虛線將會
  *                 第一個間隙的中間部分開始繪制, 如果不是很明白就請繼續(xù)往下看,
  *                 下文實戰(zhàn)部分會對虛線進行講解.
  */
- (void)setLineDash:(const CGFloat *)pattern
              count:(NSInteger)count
              phase:(CGFloat)phase;

/**
  * 該方法可以重新獲取之前設(shè)置過的虛線樣式.
  *  Note:  pattern 這個參數(shù)的容量必須大于該方法返回數(shù)組的容量.
  *         如果無法確定數(shù)組的容量, 那么可以調(diào)用兩次該方法, 第一次
  *         調(diào)用該方法的時候, 傳入 count 參數(shù), 然后在用 count 參數(shù)
  *         來申請 pattern 數(shù)組的內(nèi)存空間. 然后再第二次正常的調(diào)用該方法
  */
- (void)getLineDash:(CGFloat *)pattern 
              count:(NSInteger *)count
              phase:(CGFloat *)phase;

繪制路徑

/**
  * 該方法當(dāng)前的填充顏色 和 繪圖屬性對路徑的封閉區(qū)域進行填充.
  * 如果當(dāng)前路徑是一條開放路徑, 該方法將會隱式的將路徑進行關(guān)閉后進行填充
  * 該方法在進行填充操作之前, 會自動保存當(dāng)前繪圖的狀態(tài), 所以我們不需要
  * 自己手動的去保存繪圖狀態(tài)了. 
  */
- (void)fill;

/**
  * 該方法當(dāng)前的填充顏色 和 繪圖屬性 (外加指定的混合模式 和 透明度) 
  * 對路徑的封閉區(qū)域進行填充. 如果當(dāng)前路徑是一條開放路徑, 該方法將
  * 會隱式的將路徑進行關(guān)閉后進行填充
  * 該方法在進行填充操作之前, 會自動保存當(dāng)前繪圖的狀態(tài), 所以我們不需要
  * 自己手動的去保存繪圖狀態(tài)了. 
  *
  * @param blendMode: 混合模式?jīng)Q定了如何和已經(jīng)存在的被渲染過的內(nèi)容進行合成
  * @param alpha: 填充路徑時的透明度
  */
- (void)fillWithBlendMode:(CGBlendMode)blendMode 
                    alpha:(CGFloat)alpha;
- (void)stroke;
/**
  * @param blendMode: 混合模式?jīng)Q定了如何和已經(jīng)存在的被渲染過的內(nèi)容進行合成
  * @param alpha: 填充路徑時的透明度
  */
- (void)strokeWithBlendMode:(CGBlendMode)blendMode
                      alpha:(CGFloat)alpha;

剪切路徑

/**
  *  該方法將會修改當(dāng)前繪圖上下文的可視區(qū)域.
  *  當(dāng)調(diào)用這個方法之后, 會導(dǎo)致接下來所有的渲染
  *  操作, 只會在剪切下來的區(qū)域內(nèi)進行, 區(qū)域外的
  *  內(nèi)容將不會被渲染.
  *  如果你希望執(zhí)行接下來的繪圖時, 刪除剪切區(qū)域,
  *  那么你必須在調(diào)用該方法前, 先使用 CGContextSaveGState 方法
  *  保存當(dāng)前的繪圖狀態(tài), 當(dāng)你不再需要這個剪切區(qū)域
  *  的時候, 你只需要使用 CGContextRestoreGState 方法
  *  來恢復(fù)之前保存的繪圖狀態(tài)就可以了.
  * @param blendMode: 混合模式?jīng)Q定了如何和
  *                   已經(jīng)存在的被渲染過的內(nèi)容進行合成
  * @param alpha: 填充路徑時的透明度
  */
- (void)addClip;

Hit Detection

  • 是否包含某個點
/**
  *  該方法返回一個布爾值, 當(dāng)曲線的覆蓋區(qū)域包含
  * 指定的點(內(nèi)部點), 則返回 YES, 否則返回 NO. 
  * Note: 如果當(dāng)前的路徑是一個開放的路徑, 那么
  *       就算指定點在路徑覆蓋范圍內(nèi), 該方法仍然會
  *       返回 NO, 所以如果你想判斷一個點是否在一個
  *       開放路徑的范圍內(nèi)時, 你需要先Copy一份路徑,
  *       并調(diào)用 -(void)closePath; 將路徑封閉, 然后
  *       再調(diào)用此方法來判斷指定點是否是內(nèi)部點.
  * @param point: 指定點.
  */
- (BOOL) containsPoint:(CGPoint)point;

  • 路徑是否為空
/**
  * 檢測當(dāng)前路徑是否繪制過直線或曲線.
  * Note: 記住, 就算你僅僅調(diào)用了 moveToPoint 方法
  *       那么當(dāng)前路徑也被看做不為空.
  */
@property (readonly, getter=isEmpty) BOOL empty;

  • 路徑覆蓋的矩形區(qū)域
/**
  * 該屬性描述的是一個能夠完全包含路徑中所有點
  *  的一個最小的矩形區(qū)域. 該區(qū)域包含二次貝塞爾
  *  曲線和三次貝塞爾曲線的控制點.
  */
@property (nonatomic, readonly) CGRect bounds;

Apply Transform

/**
  * 該方法將會直接對路徑中的所有點進行指定的放射
  * 變換操作. 
  */
- (void)applyTransform:(CGAffineTransform)transform;

實例

Demo: https://github.com/iOSlixiang/Animations.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容