1. 程式人生 > >iOS中 CoreGraphics快速繪圖(詳解) 韓俊強的部落格

iOS中 CoreGraphics快速繪圖(詳解) 韓俊強的部落格

第一步:先科普一下基礎知識:

Core Graphics是基於CAPI,可以用於一切繪圖操作

Core Graphics 和Quartz 2D的區別

quartz是一個通用的術語,用於描述在IOS和MAC OS X ZHONG 整個媒體層用到的多種技術 包括圖形、動畫、音訊、適配。

Quart 2D 是一組二位繪圖和渲染API,Core Graphic會使用到這組API

Quartz Core 專指Core Animation用到的動畫相關的庫、API和類


點和畫素的對比

系統擁有座標系,如320*480 硬體有retain螢幕和非retain屏:如320*480、640*960

Core Graphics 使用的是系統的座標系來繪製圖片。在解析度為640*960手機上繪製圖片時,實際上Core Graphics 的座標是320*480。這個時候每個座標系上的點,實際上擁有兩個畫素。

圖形上下文

Core Graphics 使用圖形上下文進行工作,這個上下文的作用像畫家的畫布一樣。

在圖形上下文之外是無法繪圖的,我們可以自己建立一個上下文,但是效能和記憶體的使用上,效率是非常低得。

我們可以通過派生一個UIView的子類,獲得它的上下文。在UIView中呼叫drawRect:方法時,會自動準備好一個圖形上下文,可以通過呼叫

UIGraphicsGetCurrentContext()來獲取。 因為它是執行期間繪製圖片,我們可以動態的做一些額外的操作

Core Graphics的優點

快速、高效,減小應用的檔案大小。同時可以自由地使用動態的、高質量的圖形影象。 使用Core Graphics,可以建立直線、路徑、漸變、文字與影象等內容,並可以做變形處理。

繪製自定義檢視

drawRect:是系統的方法,不要從程式碼裡面直接呼叫 drawRect:,而應該使用setNeedsDisplay重繪.

需要知道的術語

  • 路徑 path
  • 陰影 shadow
  • 筆畫 stroke
  • 剪裁路徑 Clip Path
  • 線條粗細 Line Width
  • 混合模式 Blend Mode
  • 填充色 Fill Color
  • 當前形變矩陣 Current Transform Matrix
  • 線條圖案 Line Dash

圖形上下文

一個圖形上下文好比是畫布上的一副扁平的圖畫 執行繪畫動作,這些動作是在同一個圖層上完成的。 圖形上下文不允許將內容分不到多個圖層中,如果有需求在不同圖層上畫,可以考慮使用檢視層次結構,建立多個UIView,並將他們作為父檢視的子檢視

圖形上下文棧可以把圖形上下文的當前狀態儲存下來,並在執行一些動作後再次恢復回來

CGContextSaveGState();

CGContextStoreGState();


路徑、漸變、文字和影象

1. 使用UIBezierPath建立路徑

2. 手動建立路徑 moveToPoint addLineToPoint addArcWithCenter addCurveToPoint

漸變,漸變可以在指定方向上,以可變的比率在一系列顏色之間轉化

線性漸變:沿著一條定義好了起點和重點的直線方向,呈線性變化。如果這條線有一定角度,線性漸變也會沿相同路徑變化

放射漸變:顏色順著兩個原型之間的方向線性變化,這兩個園為起始圓和終止圓,每隔圓都有自己的圓心和班級

文字

darwAtPoint

drawInRect

影象

Core Graphics 不會保持影象的長寬比例,Core Graphics會將影象的邊界設定為CGrect,不管圖片是否變形 darwAtPoint drawInRect


第二步:程式碼部分:

基礎畫法就不多講啦!都通用:

第一種繪圖形式:在UIView的子類方法drawRect:中繪製一個藍色圓,使用UIKit在Cocoa為我們提供的當前上下文中完成繪圖任務。 
    - (void) drawRect: (CGRect) rect { 
     
    UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
     
    [[UIColor blueColor] setFill]; 
     
    [p fill]; 
     
    } 

第二種繪圖形式:使用Core Graphics實現繪製藍色圓。 
    - (void) drawRect: (CGRect) rect { 
     
    CGContextRef con = UIGraphicsGetCurrentContext(); 
     
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
     
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
     
    CGContextFillPath(con); 
     
    } 

第三種繪圖形式:我將在UIView子類的drawLayer:inContext:方法中實現繪圖任務。drawLayer:inContext:方法是一個繪製圖層內容的代理方法。為了能夠呼叫drawLayer:inContext:方法,我們需要設定圖層的代理物件。但要注意,不應該將UIView物件設定為顯示層的委託物件,這是因為UIView物件已經是隱式層的代理物件,再將它設定為另一個層的委託物件就會出問題。輕量級的做法是:編寫負責繪圖形的代理類。在MyView.h檔案中宣告如下程式碼: 
    @interface MyLayerDelegate : NSObject 
     
    @end 

然後MyView.m檔案中實現介面程式碼: 
    @implementation MyLayerDelegate 
     
    - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { 
     
      UIGraphicsPushContext(ctx); 
     
      UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
     
      [[UIColor blueColor] setFill]; 
     
      [p fill]; 
     
      UIGraphicsPopContext(); 
     
    } 
     
    @end 

直接將代理類的實現程式碼放在MyView.m檔案的#import程式碼的下面,這樣感覺好像在使用私有類完成繪圖任務(雖然這不是私有類)。需要注意的是,我們所引用的上下文並不是當前上下文,所以為了能夠使用UIKit,我們需要將引用的上下文轉變成當前上下文。因為圖層的代理是assign記憶體管理策略,那麼這裡就不能以區域性變數的形式建立MyLayerDelegate例項物件賦值給圖層代理。這裡選擇在MyView.m中增加一個例項變數,因為例項變數預設是strong: 
    @interface MyView () { 
     
    MyLayerDelegate* _layerDeleagete; 
     
    } 
     
    @end 

使用該圖層代理: 
    MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)]; 
     
    CALayer *myLayer = [CALayer layer]; 
     
    _layerDelegate = [[MyLayerDelegate alloc] init]; 
     
    myLayer.delegate = _layerDelegate; 
     
    [myView.layer addSublayer:myLayer]; 
     
    [myView setNeedsDisplay]; // 呼叫此方法,drawLayer: inContext:方法才會被呼叫。 

第四種繪圖形式: 使用Core Graphics在drawLayer:inContext:方法中實現同樣操作,程式碼如下: 
    - (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con { 
     
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
     
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
     
    CGContextFillPath(con); 
     
    } 

最後,演示UIGraphicsBeginImageContextWithOptions的用法,並從上下文中生成一個UIImage物件。生成UIImage物件的程式碼並不需要等待某些方法被呼叫後或在UIView的子類中才能去做。第五種繪圖形式: 使用UIKit實現: 
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 
     
    UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
     
    [[UIColor blueColor] setFill]; 
     
    [p fill]; 
     
    UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
     
    UIGraphicsEndImageContext(); 

解釋一下UIGraphicsBeginImageContextWithOptions函式引數的含義:第一個引數表示所要建立的圖片的尺寸;第二個引數用來指定所生成圖片的背景是否為不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會是黑色,顯然這不是我想要的;第三個引數指定生成圖片的縮放因子,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據螢幕的解析度而變化,所以我們得到的圖片不管是在單解析度還是視網膜屏上看起來都會很好。第六種繪圖形式: 使用Core Graphics實現: 
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 
     
    CGContextRef con = UIGraphicsGetCurrentContext(); 
     
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
     
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
     
    CGContextFillPath(con); 
     
    UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
     
    UIGraphicsEndImageContext(); 

第三步:實踐部分:

第一種:基本圖形繪製

/**
 *  什麼呼叫:當你檢視第一次顯示的時候就會呼叫
 *  作用:繪圖
 *  @param rect = self.bounds
 */
- (void)drawRect:(CGRect)rect
{
    // 1.獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2.拼接路徑
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    CGPoint startP = CGPointMake(10, 125);
    CGPoint endP = CGPointMake(240, 125);
    CGPoint controlP = CGPointMake(125, 0);
    [path moveToPoint:startP];
    [path addQuadCurveToPoint:endP controlPoint:controlP];
    
    // 3.把路徑新增到上下文
    CGContextAddPath(ctx, path.CGPath);
    
    // 4.渲染上下文到檢視
    CGContextStrokePath(ctx);
}

- (void)drawLine
{
    // 1.獲取上下文
    // CGContextRef CG CoreGraphics Ref 引用
    // 目前學的上下文都跟UIGraphics有關,以後想直接獲取上下文,直接敲一個UIGraphics
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2.設定繪圖資訊(拼接路徑)
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    // 設定起點
    [path moveToPoint:CGPointMake(10, 10)];
    
    // 新增一條線到某個點
    [path addLineToPoint:CGPointMake(125, 125)];
    [path addLineToPoint:CGPointMake(240, 10)];
    // 3.把路徑新增到上下文
    // 直接把UIKit的路徑轉換成CoreGraphics,CG開頭就能轉
    CGContextAddPath(ctx, path.CGPath);
    
    // 4.把上下文渲染到檢視
    // Stroke描邊
    CGContextStrokePath(ctx);
}

- (void)draw2Line
{
    // 1.獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2.拼接路徑
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    // 設定起點
    [path moveToPoint:CGPointMake(10, 125)];
    
    // 新增一條線到某個點
    [path addLineToPoint:CGPointMake(230, 125)];
    
    //    // 設定起點
    //    [path moveToPoint:CGPointMake(10, 10)];
    //
    //    // 新增一條線到某個點
    //    [path addLineToPoint:CGPointMake(125, 100)];
    
    UIBezierPath *path1 = [UIBezierPath bezierPath];
    
    [path1 moveToPoint:CGPointMake(10, 10)];
    
    [path1 addLineToPoint:CGPointMake(125, 100)];
    
    
    // 3.把路徑新增到上下文
    CGContextAddPath(ctx, path.CGPath);
    CGContextAddPath(ctx, path1.CGPath);
    
    // 設定繪圖狀態
    // 設定線寬
    CGContextSetLineWidth(ctx, 10);
    CGContextSetLineCap(ctx, kCGLineCapRound);
    //    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
    [[UIColor redColor] set];
    
    // 4.渲染上下文到檢視
    CGContextStrokePath(ctx);

}


第二種:下載進度條:

- (void)setProgress:(CGFloat)progress
{
    _progress = progress;
    self.myLabel.text = [NSString stringWithFormat:@"%.2f%%",progress*100];
    //    [self drawRect:self.bounds];
    // 重新繪製
    // 在view上做一個重繪的標記,當下次螢幕重新整理的時候,就會呼叫drawRect.
    [self setNeedsDisplay];
}


// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.

 // 當檢視顯示的時候會呼叫 預設只會呼叫一次
- (void)drawRect:(CGRect)rect
{
     // 1.獲取上下文
     CGContextRef ctx = UIGraphicsGetCurrentContext();
     
     // 2.拼接路徑
     CGPoint center = CGPointMake(50, 50);
     CGFloat radius = 50 - 2;
     CGFloat startA = -M_PI_2;
     CGFloat endA = -M_PI_2 + _progress * M_PI * 2;
     UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
     
     // 3.把路徑新增到上下文
     CGContextAddPath(ctx, path.CGPath);
     
     // 4.把上下文渲染到檢視
     CGContextStrokePath(ctx);
 
}


第三種:餅圖

- (void)drawRect:(CGRect)rect
{
    // Drawing code
    
    NSArray *data = @[@25,@25,@50];
    
    // 1.獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2.拼接路徑
    CGPoint center = CGPointMake(125, 125);
    CGFloat radius = 120;
    CGFloat startA = 0;
    CGFloat angle = 0;
    CGFloat endA = 0;
    
    for (NSNumber *number in data) {
        // 2.拼接路徑
        startA = endA;
        angle = number.intValue / 100.0 * M_PI * 2;
        endA = startA + angle;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
        [path addLineToPoint:center];
        
        [[UIColor randomColor] set];
        // 把路徑新增上下文
        CGContextAddPath(ctx, path.CGPath);
        
        // 渲染
        CGContextFillPath(ctx);
        
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGFloat a = arc4random_uniform(6);
    //CGFloat a =  arc4random()%6;
    NSLog(@"隨機數--%f",a);
    
    
    [self setNeedsDisplay];
}

- (void)drawPie
{
    // 1.獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 2.拼接路徑
    CGPoint center = CGPointMake(125, 125);
    CGFloat radius = 120;
    CGFloat startA = 0;
    CGFloat angle = 0;
    CGFloat endA = 0;
    
    // 第一個扇形
    angle = 25 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path addLineToPoint:center];
    // 新增到上下文
    CGContextAddPath(ctx, path.CGPath);
    [[UIColor redColor] set];
    
    
    // 渲染
    CGContextFillPath(ctx);
    
    
    
    // 第二個扇形
    startA = endA;
    angle = 25 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path1 addLineToPoint:center];
    // 新增到上下文
    CGContextAddPath(ctx, path1.CGPath);
    [[UIColor greenColor] set];
    // 渲染
    CGContextFillPath(ctx);
    
    // 第三個扇形
    startA = endA;
    angle = 50 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path2 addLineToPoint:center];
    // 新增到上下文
    CGContextAddPath(ctx, path2.CGPath);
    [[UIColor blueColor] set];
    // 渲染
    CGContextFillPath(ctx);
    
}



第四種:柱形圖

- (void)drawRect:(CGRect)rect
{
     NSArray *data = @[@25,@25,@50];
     NSInteger count = data.count;
     
     CGFloat w = rect.size.width / (2 * count - 1);
     CGFloat h = 0;
     CGFloat x = 0;
     CGFloat y = 0;
     CGFloat viewH = rect.size.height;
     // 1.獲取上下文
     CGContextRef ctx = UIGraphicsGetCurrentContext();
     
     for (int i = 0; i < count; i++) {
     h = viewH * [data[i] intValue] / 100.0;
     x = 2 * w * i;
     y = viewH - h;
     // 2.拼接路徑
     UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
     
     // 3.新增路徑到上下文
     CGContextAddPath(ctx, path.CGPath);
     
     [[UIColor randomColor] set];
     
     // 4.渲染
     CGContextFillPath(ctx);
     }
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
     [self setNeedsDisplay];
}



第五種:模仿UIImageView

- (void)setImage:(UIImage *)image
 {
     _image = image;
     [self setNeedsDisplay];
}
 
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
 // Drawing code
 
     [_image drawInRect:rect];
}


第六種:圖形上下文線
- (void)drawRect:(CGRect)rect
{
    // Drawing code
    
    // 1.獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 把ctx拷貝一份放在棧中
    CGContextSaveGState(ctx);
    
    // 2.拼接路徑(繪圖的資訊)
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(10, 125)];
    [path addLineToPoint:CGPointMake(240, 125)];
    
    // 3.路徑新增到上下文
    CGContextAddPath(ctx, path.CGPath);
    
    // 設定繪圖的狀態
    [[UIColor redColor] set];
    CGContextSetLineWidth(ctx, 10);
    CGContextSetLineCap(ctx, kCGLineCapRound);
    
    // 4.渲染
    CGContextStrokePath(ctx);
    
    
    // 第二根線
    UIBezierPath *path1 = [UIBezierPath bezierPath];
    [path1 moveToPoint:CGPointMake(125, 10)];
    [path1 addLineToPoint:CGPointMake(125, 240)];
    CGContextAddPath(ctx, path1.CGPath);
    
    // 把棧頂上下文取出來,替換當前上下文
    CGContextRestoreGState(ctx);
    
    // 設定繪圖的狀態
    //    [[UIColor blackColor] set];
    //    CGContextSetLineWidth(ctx, 1);
    //    CGContextSetLineCap(ctx, kCGLineCapButt);
    
    
    // 4.渲染
    CGContextStrokePath(ctx);
    
}



第七種:矩形操作

- (void)drawRect:(CGRect)rect
{
    // Drawing code
    
    // 1.獲取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 注意:你的路徑一定放在上下文矩陣操作之後
    // 平移上下文
    CGContextTranslateCTM(ctx, 50, 100);
    
    // 旋轉上下文
    CGContextRotateCTM(ctx, M_PI_4);
    
    // 縮放上下文
    CGContextScaleCTM(ctx, 0.5, 1.2);
    
    // 2.拼接路徑
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-50, -100, 150, 200)];
    
    // 3.把路徑新增到上下文
    CGContextAddPath(ctx, path.CGPath);
    
    
    
    [[UIColor redColor] set];
    
    // 4.渲染
    CGContextFillPath(ctx);
    
}


iOS開發者交流群:446310206

iOS開發者交流QQ群: 446310206  歡迎加入(demo在這裡)!