iOS - Quartz2D & CALayer & CAShapeLayer

自己學習用的,做一個簡單的匯總。


1.Quartz2D

提起iOS中的繪圖控件,必然會想到Quartz2D。Quartz 2D是?個二維繪圖引擎,同時支持iOS和Mac系統(tǒng)。Quartz2D的API來自于CoreGraphics框架,數(shù)據(jù)類型和函數(shù)基本都以CG作為前綴,如CGContextRef、CGPathRef。
Quartz 2D能完成的工作:

  • 繪制圖形 : 線條\三角形\矩形\圓\弧等
  • 繪制文字
  • 繪制\生成圖片(圖像)
  • 讀取\生成PDF
  • 截圖\裁剪圖片
  • 自定義UI控件

一般在開發(fā)中,給圖片添加水印,合并圖片,自定義UI控件(UIKit框架中沒有的)等。
一般情況下,通過蘋果官方的UIKit庫就可以搭建常見的UI界面了,但是如果通過UIKit框架不能實現(xiàn)的一些UI界面,就需要我們自己來通過Quartz2D進行繪制了。
自定義UI控件是Quartz2D中非常重要的一個功能

使用Quartz2D 繪圖有2種方式:
方式一:直接調(diào)用Quartz2D的API 進行繪圖
方式二:調(diào)用 UIKit 框架封裝好的 API 進行繪圖(代碼簡單,但是只能是文字和圖片)

繪圖的基本步驟:
1.開啟圖形上下文(如果是自定義view,不需要這一步,但是必須在drawRect方法中獲取context,在這里才能獲取和view相關(guān)聯(lián)的context)

UIGraphicsBeginImageContext(size)

2.獲取圖形上下文

CGContextRef context = UIGraphicsGetCurrentContext();

3.設(shè)置上下文的一些屬性(顏色,線寬)

CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, 2.0);   
CGContextSetLineJoin(context, kCGLineJoinRound);// 設(shè)置連接處的樣式
CGContextSetLineCap(context, kCGLineCapRound);// 設(shè)置頭尾的樣式

//UIColor本身提供了setStroke,setFill的方法
[[UIColor purpleColor] setStroke];

4.拼接路徑(繪制圖形)

1. 
    CGContextMoveToPoint(context, 50, 50);
    CGContextAddLineToPoint(context, 100, 100);

2.
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 100, 100);
    CGPathAddLineToPoint(path, NULL, 200, 200);
    CGContextAddPath(context, path);

3.
    UIBezierPath* path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(100, 100)];
    [path addLineToPoint:CGPointMake(200, 200)];
    CGContextAddPath(context, path.CGPath);

4.純OC方法
    // 創(chuàng)建路徑對象
    UIBezierPath* path = [UIBezierPath bezierPath];
    // 拼接
    [path moveToPoint:CGPointMake(100, 100)];
    [path addLineToPoint:CGPointMake(200, 200)];
    [path setLineWidth:30];
    [path setLineJoinStyle:kCGLineJoinBevel];

    // 通過路徑對象 渲染
    [path stroke];

5.渲染

CGContextStrokePath(context);
CGContextFillPath(context);
CGContextEOFillPath(context);
CGContextDrawPath(context,drawModel);               //很多的模式,stroke、fill....
CGContextStrokeRectWithWidth(context.lineWidth);//stroke的同時,設(shè)置了線寬
CGContextStrokeLineSegments                              //畫線段
[UIBezierPath stroke];

6.關(guān)閉上下文(關(guān)閉路徑)

CGContextClosePath(context)
- (void)closePath;                      //oc
UIGraphicsEndImageContext()

iOS中提供了繪制各種圖形的方法

1.C代碼
CGContextAddLineToPoint //直線
CGContextAddCurveToPoint //曲線
CGContextAddQuadCurveToPoint //四方曲線(暫時先這樣翻譯吧)
CGContextAddRect //矩形
CGContextAddEllipseInRect //橢圓
CGContextAddArc //圓弧

2.OC代碼(UIBezierPath)

- (void)moveToPoint:(CGPoint)point;
- (void)addLineToPoint:(CGPoint)point;
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise

示例代碼:

  1. 創(chuàng)建圓形圖片的方法:關(guān)于這塊有挺多東西的,需要的話可以參考iOS 高效添加圓角效果實戰(zhàn)講解,這篇帖子的評論部分也提供了好多別人的框架
- (instancetype)circleImage  {
    // 開啟圖形上下文
    UIGraphicsBeginImageContext(self.size);
    // 獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 添加一個圓
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextAddEllipseInRect(ctx, rect);
    // 裁剪
    CGContextClip(ctx);
    // 繪制圖片
    [self drawInRect:rect];  
    // 獲得圖片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // 關(guān)閉圖形上下文
    UIGraphicsEndImageContext(); 
    return image;
}

自定義view(自定義UI控件)
?定義view的步驟:
(1)新建?個類,繼承自UIView
(2)實現(xiàn)-(void)drawRect:(CGRect)rect?法

注:1.繪制的方法必須寫在drawRect:方法中,只有在這里才能獲取和view相關(guān)聯(lián)的上下文
2.drawRect:什么時候調(diào)用(不能自己調(diào)用,只能通過系統(tǒng)調(diào)用這個方法)。
當view第一次顯示到屏幕上時(被加到UIWindow上顯示出來)
調(diào)用view的setNeedsDisplay或者setNeedsDisplayInRect:時

自定義圓形進度條(drawRect實現(xiàn)):
1.進度值 -- 開放一個progressValue屬性
2.根據(jù)進度值,重繪UI界面 --[self setNeedsDisplay],然后系統(tǒng)會自動調(diào)用drawRect方法

- (void)setProgressValue:(CGFloat)progressValue {
    _progressValue = progressValue;
    [self setNeedsDisplay];// 重繪
}

- (void)drawRect:(CGRect)rect {
    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:self.center radius:self.frame.size.width < self.frame.size.height ? self.frame.size.width * 0.3 : self.frame.size.height * 0.3 startAngle:-M_PI_2 endAngle:2 * M_PI * self.progressValue - M_PI_2 clockwise:1];

    [path setLineWidth:3];
    [[UIColor blueColor] setStroke];
    
    [path stroke];// 渲染
}

注:在drawRect方法中繪圖,
1.不需要創(chuàng)建圖形上下文。 -- 這里可以直接獲取到和view相關(guān)聯(lián)的上下文
2.使用UIBezierPath(OC方法),不需要獲取圖形上下文,內(nèi)部蘋果已經(jīng)封裝了
3.不需要關(guān)閉上下文

圓形進度條,在這里其實就可以和SDWebImage結(jié)合起來,實現(xiàn)一個圖片加載的進度顯示了。這里貼一個SDWebImage進度顯示簡單Demo


2. CALayer

在iOS中CALayer的設(shè)計主要是了為了內(nèi)容展示動畫操作,CALayer本身并不包含在UIKit中,它不能響應事件。由于CALayer在設(shè)計之初就考慮它的動畫操作功能,CALayer很多屬性在修改時都能形成動畫效果,這種屬性稱為“隱式動畫屬性”。但是對于UIView的根圖層而言屬性的修改并不形成動畫效果,因為很多情況下根圖層更多的充當容器的做用,如果它的屬性變動形成動畫效果會直接影響子圖層。另外,UIView的根圖層創(chuàng)建工作完全由iOS負責完成,無法重新創(chuàng)建,但是可以往根圖層中添加子圖層或移除子圖層。

CALayer的基本使用

  1. 設(shè)置圓角
view.layer.cornerRadius = 5;
view.layer.maskToBound = YES; 

注意,如果設(shè)置了maskToBounds=YES,那將不會有陰影效果

  1. 設(shè)置邊框
view.layer.boarderWidth = 5;
view.layer.boarderColor = [UIColor redColor].CGColor;
  1. 設(shè)置旋轉(zhuǎn)
view.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
  1. 設(shè)置陰影
view.layer.shadowColor = [UIColor grayColor].CGColor;
view.layer.shadowOffset = CGSizeMake(10, 10);
view.layer.shadowOpacity = 0.5;

注意,如果設(shè)置了maskToBounds=YES,那將不會有陰影效果

  1. 設(shè)置背景圖片
view.layer.content = [UIImage iamgeNamed:@"xxx"].CGImage;

自定義layer

layer和view是緊密相連的,所以他們的操作很相似。自定義layer需要重寫drawInContext方法,自定義view需要重寫drawRect方法。針對view的操作最終都會作用到layer上

- (void)drawInContext:(CGContextRef)ctx {
    // 設(shè)置為藍色
    CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);
    
    //繪制圖形的方法和view是一樣的都是使用Quartz2D,參考Quartz2D部分

    // 渲染
    CGContextFillPath(ctx);
}

注意:需要刷新控件時,不能自己手動調(diào)用這個方法,而是使用[self setNeedsDisplay]

CALayer的mask屬性

mask屬性之前從來沒有使用過,感覺很陌生,所以特意找了一下這方面的。使用CALayer的Mask實現(xiàn)注水動畫效果這個效果很不錯,直接拿過來了

mask實際上是layer內(nèi)容的一個遮罩,如果我們把mask設(shè)置成透明的,實際看到的layer是完全透明的(這個layer就看不到了),也就是說只有mask的內(nèi)容不透明的部分和layer疊加的部分才會顯示出來,效果如下:

20141117_02.jpg

一張圖片說明了一切,代碼就不貼了,直接參考原作者的吧。
設(shè)計的思路參考《基于Core Animation的KTV歌詞視圖的平滑實現(xiàn)》,Facebook Shimmer。
20141117_03-2.jpg

CALayer的動畫 -- Core Animation

這個內(nèi)容就太多了,動畫部分不多說,注意:核心動畫作用在layer上。
貼幾篇動畫相關(guān)文章
iOS動畫-從UIView動畫說起
iOS動畫-Transform和KeyFrame動畫
iOS動畫-layout動畫初體驗
iOS動畫-layout動畫的更多使用
iOS動畫-碎片動畫
iOS動畫-認識CoreAnimation


3. CAShapLayer

  1. CAShapeLayer繼承自CALayer,可使用CALayer的所有屬性
  2. CAShapeLayer需要和貝塞爾曲線配合使用才有意義。
    在蘋果的General Tips and Tricks可以找到這句話
The CAShapeLayer class creates its content by rendering the path you provide into a bitmap image at composite time.
  1. 使用CAShapeLayer與貝塞爾曲線可以實現(xiàn)不在view的DrawRect方法中畫出一些想要的圖形
The CAShapeLayer class draws a cubic Bezier spline in its coordinate space. 
//CAShapeLayer這個類是用來繪制三次貝塞爾曲線的

CAShapeLayer結(jié)合貝塞爾曲線(UIBezierPath)**

UIBezierPath在Quartz2D部分有介紹,CGPathRef是基于C語言的路徑,UIBezierPath是OC版的,用法很類似。他們都是用來繪制曲線

繪制的方法

+ (instancetype)bezierPath;
+ (instancetype)bezierPathWithRect:(CGRect)rect;
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
+ (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;

- (void)moveToPoint:(CGPoint)point;
- (void)addLineToPoint:(CGPoint)point;
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise

示例代碼:

1. 自定義圓形進度條(CAShapLayer實現(xiàn)):

CAShapLayer中有兩個非常關(guān)鍵的屬性strokeEnd,strokeStart。自定義圓形進度條(drawRect實現(xiàn))中,我們是通過調(diào)用[self setNeedDisplay]方法來實現(xiàn)視圖的繪制的,但是在CAShapLayer中,直接設(shè)置strokeEnd就可以直接自動的去繪制視圖更新,不需要我們操作什么,就像設(shè)置view的frame,bounds等一樣,系統(tǒng)自動幫我們做了。

Untitled222.gif
//
//  CustomProgressLayer.m
//  CAShapeLayer實現(xiàn)圓形進度條
//
//  Created by yangguangyu on 16/8/31.
//  Copyright ? 2016年 yangguangyu. All rights reserved.
//

#import "CustomProgressLayer.h"

@implementation CustomProgressLayer

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}

-(void)setup {
    self.strokeColor = [UIColor redColor].CGColor;
    self.fillColor = [UIColor clearColor].CGColor;
    self.lineWidth = 5;
    self.strokeStart = 0;
    self.strokeEnd = 0;
}


-(void)addProgressLayerOnView:(UIView *)view {
    self.frame = view.bounds;
    
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5) radius:self.frame.size.width * 0.1 startAngle:0 endAngle:2 * M_PI clockwise:YES];
    self.path = path.CGPath;
    
    [view.layer addSublayer:self];
}

-(void)setProgressValue:(CGFloat)progressValue {
    _progressValue = progressValue;
    
    if (progressValue < 0) {
        progressValue = 0;
    }else if (progressValue > 1) {
        progressValue = 1;
    }
    
    self.strokeEnd = progressValue;
}
@end

www.raywenderlich.com官網(wǎng)上,有一個非常不錯的圓形圖片加載動畫

1434094036762587.gif

前面我們已經(jīng)實現(xiàn)了圓環(huán)的效果,只需要在圖片加載完成的時候添加一個動畫

-(void)reveal {
    self.superlayer.mask = self;

    //動畫
    CABasicAnimation *lineAnimation = [CABasicAnimation animationWithKeyPath:@"lineWidth"];
//    lineAnimation.toValue = @(300);//這里決定了中間最后狀態(tài)時中心圓環(huán)的大小
    lineAnimation.toValue = @(self.bounds.size.width);
    
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    UIBezierPath *toPath = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
    pathAnimation.toValue = toPath;//這里決定了最外層

    CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
    group.animations = @[lineAnimation,pathAnimation];
    group.duration = 2;
    group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    [self addAnimation:group forKey:nil];

}

原版的是swift的,原貼How To Implement A Circular Image Loader Animation with CAShapeLayer,自己用OC也寫了一份demo

2. 繪制虛線(不用CAShaprLayer也可以實現(xiàn),直接使用Quartz2D)

/**
 ** lineView:       需要繪制成虛線的view
 ** lineLength:     虛線的寬度
 ** lineSpacing:    虛線的間距
 ** lineColor:      虛線的顏色
 **/
+ (void)drawDashLine:(UIView *)lineView lineLength:(int)lineLength lineSpacing:(int)lineSpacing lineColor:(UIColor *)lineColor
{
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    [shapeLayer setBounds:lineView.bounds];
    [shapeLayer setPosition:CGPointMake(CGRectGetWidth(lineView.frame) / 2, CGRectGetHeight(lineView.frame))];
    [shapeLayer setFillColor:[UIColor clearColor].CGColor];
     
    //  設(shè)置虛線顏色為blackColor
    [shapeLayer setStrokeColor:lineColor.CGColor];
     
    //  設(shè)置虛線寬度
    [shapeLayer setLineWidth:CGRectGetHeight(lineView.frame)];
    [shapeLayer setLineJoin:kCALineJoinRound];
     
    //  設(shè)置線寬,線間距
    [shapeLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:lineLength], [NSNumber numberWithInt:lineSpacing], nil]];
     
    //  設(shè)置路徑
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 0, 0);
    CGPathAddLineToPoint(path, NULL, CGRectGetWidth(lineView.frame), 0);
     
    [shapeLayer setPath:path];
    CGPathRelease(path);
     
    //  把繪制好的虛線添加上來
    [lineView.layer addSublayer:shapeLayer];
}

3. 給UIView添加分隔線

在很多情況下,我們需要給UIView添加一個分隔線,尤其是使用UITableView的時候,經(jīng)常會自己搞分割線,以前我的做法是在cell的底部添加一個高度為1的UIView,使用CAShapeLayer或者CALayer可以不用再添加view了,直接搞一個layer上去。寫一個UIView的分類

//
//  UIView+BorderLine.m
//  CAShapeLayer分割線
//
//  Created by yangguangyu on 16/9/2.
//  Copyright ? 2016年 yangguangyu. All rights reserved.
//

#import "UIView+BorderLine.h"


@implementation UIView (BorderLine)

-(void)addBorderTopLine:(CGSize)size andColor:(UIColor *)color {
    [self addBorderLineWithframe:CGRectMake(0, 0 , size.width, size.height) andColor:color];
}

-(void)addBorderLeftLine:(CGSize)size andColor:(UIColor *)color {
    [self addBorderLineWithframe:CGRectMake(0, 0, size.width, size.height) andColor:color];
}

-(void)addBorderBottomLine:(CGSize)size andColor:(UIColor *)color {
    [self addBorderLineWithframe:CGRectMake(0, self.frame.size.height - size.height, size.width, size.height) andColor:color];
}

-(void)addBorderRightLine:(CGSize)size andColor:(UIColor *)color {
    [self addBorderLineWithframe:CGRectMake(self.frame.size.width - size.width, 0, size.width, size.height) andColor:color];
}

#pragma mark - padding


-(void)addBorderTopLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
    [self addBorderLineWithframe:CGRectMake(padding, 0 , size.width - 2 * padding, size.height) andColor:color];
}

-(void)addBorderLeftLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
    [self addBorderLineWithframe:CGRectMake(0, padding, size.width, size.height - 2 * padding) andColor:color];
}

-(void)addBorderBottomLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
    [self addBorderLineWithframe:CGRectMake(padding, self.frame.size.height - size.height, size.width - 2 * padding, size.height) andColor:color];
}

-(void)addBorderRightLine:(CGSize)size andColor:(UIColor *)color andPadding:(CGFloat)padding {
    [self addBorderLineWithframe:CGRectMake(self.frame.size.width - size.width, padding, size.width, size.height - 2 * padding) andColor:color];
}

//-------

-(void)addBorderLineWithframe:(CGRect)frame andColor:(UIColor *)color {
    
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.frame = frame;
    layer.backgroundColor = color.CGColor;
    [self.layer addSublayer:layer];
}
@end

參考文章:

OS開發(fā)UI篇—Quartz2D簡單介紹
使用CALayer的Mask實現(xiàn)注水動畫效果
iOS開發(fā)之讓你的應用“動”起來崔江濤的教程都很好,講的非常全面
How To Implement A Circular Image Loader Animation with CAShapeLayer
放肆的使用UIBezierPath和CAShapeLayer畫各種圖形
關(guān)于CAShapeLayer的一些實用案例和技巧
基于CAShapeLayer和貝塞爾曲線的圓形進度條動畫【原創(chuàng)】

最后編輯于
?著作權(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)容

  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,686評論 6 30
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,026評論 4 61
  • 轉(zhuǎn)載:http://www.itdecent.cn/p/32fcadd12108 每個UIView有一個伙伴稱為l...
    F麥子閱讀 6,567評論 0 13
  • 每個UIView有一個伙伴稱為layer,一個CALayer。UIView實際上并沒有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,250評論 0 17
  • 這一個章節(jié)是到目前為止本書最長的一個章節(jié)。原因也很簡單:因為檢視閱讀算是需要理解的主動閱讀,是有限時間內(nèi)快速理解整...
    藥山閱讀 1,939評論 0 51

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