UI基礎 - Quartz2D 02:圖形上下文棧 | 繪圖路徑
圖形上下文棧
1 - 先來看一段程式碼
① 繪製兩條線,其中為第一條線設定狀態
1 - (void)drawRect:(CGRect)rect{ 2 3 // 獲取上下文 4 CGContextRef ctx=UIGraphicsGetCurrentContext(); 5 6 // 第一條線 7 CGContextMoveToPoint(ctx, 20, 100); 8 CGContextAddLineToPoint(ctx, 100, 320); 9 10 CGContextSetLineWidth(ctx, 12);// // 線條寬度11 [[UIColor brownColor]set];// 線條顏色 12 CGContextSetLineCap(ctx,kCGLineCapRound);// 線條兩端的樣式為圓角 13 CGContextStrokePath(ctx); // 渲染 14 15 // 第二條線 16 CGContextMoveToPoint(ctx, 40, 200); 17 CGContextAddLineToPoint(ctx, 80, 100); 18 CGContextStrokePath(ctx); 19 20 }
執行效果:你會發現第二條線也被渲染成第一條線的樣式和狀態
② 要讓兩條線的顏色不一樣且第二條線變成原版的樣子
(1)方式一:在對第二條線進行設定的時候,清空狀態
1 - (void)drawRect:(CGRect)rect{ 2 3 CGContextRef ctx=UIGraphicsGetCurrentContext(); 4 // 第一條線 5 CGContextMoveToPoint(ctx, 20, 100); 6 CGContextAddLineToPoint(ctx, 100, 320); 7 CGContextSetLineWidth(ctx, 12); 8 [[UIColor brownColor]set]; 9 CGContextSetLineCap(ctx,kCGLineCapRound); 10 CGContextStrokePath(ctx); 11 12 // 第二條線 13 CGContextMoveToPoint(ctx, 40, 200); 14 CGContextAddLineToPoint(ctx, 80, 100); 15 // 清空狀態:重設預設狀態 16 CGContextSetLineWidth(ctx, 1); 17 [[UIColor blackColor]set]; 18 CGContextSetLineCap(ctx,kCGLineCapButt); 19 CGContextStrokePath(ctx); 20 21 }
(2)方式二:把第一條線從開始繪製到渲染的程式碼剪下到第二條線渲染完成之後
1 - (void)drawRect:(CGRect)rect{ 2 // 獲取上下文 3 CGContextRef ctx=UIGraphicsGetCurrentContext(); 4 // 第二條線 5 CGContextMoveToPoint(ctx, 40, 200); 6 CGContextAddLineToPoint(ctx, 80, 100); 7 CGContextStrokePath(ctx); 8 9 // 第一條線 10 CGContextMoveToPoint(ctx, 20, 100); 11 CGContextAddLineToPoint(ctx, 100, 320); 12 CGContextSetLineWidth(ctx, 12); 13 [[UIColor brownColor]set]; 14 CGContextSetLineCap(ctx,kCGLineCapRound); 15 CGContextStrokePath(ctx); 16 }
執行效果:方式一/方式二
其實,無論是哪種方式,都算不上解決問題:如果有情況,必須要先畫第一條線再畫第二條線且要求在交叉部分,第二條線蓋在第一條線的上面,那麼只能使用第一種做法;如果有新的情況,要求在這個基礎上再畫兩條線,那就需要清空上下文中的狀態很多次!為了解決這個問題,下面給大家介紹圖形上下文棧
2 - 圖形上下文以理解為在上下文中有一塊單獨的區域用來先繪製圖形,當呼叫渲染方法的時候,才會把繪製好的圖形顯示到 view 上去,就是說在繪製圖形區域時,上下文會儲存繪圖狀態資訊(線寬、圓角、顏色等等),其實在渲染之前就已經把所要繪製的圖形在繪圖區畫好了
如果沒有對繪圖狀態進行重新設定,那麼當畫完第一條線的時候使用的繪圖狀態還儲存在圖形上下文中,在第二條線進行渲染之前,會根據第一條線(上一份繪圖狀態)對第二條線進行相應的設定,渲染後把第二條線顯示到螢幕上,這就是剛開始的程式碼效果圖的原因
3 - 在獲取圖形上下文之後,通過 CGContextSaveGState(ctx) 方法把當前獲取的上下文拷貝一份,儲存一份圖形上下文。在畫第二條線之前使用CGContextRestoreGState(ctx) 方法還原開始時儲存的那份圖形上下文
1 - (void)drawRect:(CGRect)rect{ 2 3 CGContextRef ctx=UIGraphicsGetCurrentContext(); 4 // 儲存一份最初的圖形上下文 5 CGContextSaveGState(ctx); 6 7 //第一條線 8 CGContextMoveToPoint(ctx, 20, 100); 9 CGContextAddLineToPoint(ctx, 100, 320); 10 CGContextSetLineWidth(ctx, 12); 11 [[UIColor brownColor]set]; 12 CGContextSetLineCap(ctx,kCGLineCapRound); 13 CGContextStrokePath(ctx); 14 15 // 還原開始時的圖形上下文 16 CGContextRestoreGState(ctx); 17 // 第二條線 18 CGContextMoveToPoint(ctx, 40, 200); 19 CGContextAddLineToPoint(ctx, 80, 100); 20 CGContextStrokePath(ctx); 21 22 // // 注:上下文在棧裡儲存了幾次,就可以取幾次 23 // // 比如這裡儲存了 1 次,卻取出了 2 次,那麼在取第 2 次的時候,因為棧裡為空就會直接掛掉 24 // CGContextRestoreGState(ctx);// 超次取出,程式崩潰 25 // CGContextMoveToPoint(ctx, 140, 300); 26 // CGContextAddLineToPoint(ctx, 180, 130); 27 // CGContextStrokePath(ctx); 28 29 }
繪圖路徑
1 - 在畫線的時候,方法的內部預設建立一個path,它把路徑都放到了path裡面
① 建立路徑:CGMutablePathRef 呼叫該方法相當於建立了一個路徑,這個路徑用來儲存繪圖資訊
② 把繪圖資訊新增到路徑裡:以前的方法是點的位置新增到圖形上下文中,預設會在內部建立一個 path 用來儲存繪圖資訊,其實在圖形上下文中有一塊儲存空間專門用來儲存繪圖資訊,這塊空間就是 CGMutablePathRef
2 - 程式碼示例
① 使用 path
1 - (void)drawRect:(CGRect)rect{ 2 3 CGContextRef ctx = UIGraphicsGetCurrentContext(); 4 5 // 建立一條直線繪圖的路徑(但凡通過 Quartz2D 中帶有 creat、copy、retain方法創建出來的值都必須要釋放) 6 CGMutablePathRef path = CGPathCreateMutable(); 7 CGPathMoveToPoint(path, NULL, 20, 20); 8 CGPathAddLineToPoint(path, NULL, 200, 300); 9 CGContextAddPath(ctx, path); 10 CGContextStrokePath(ctx); 11 12 // 釋放前面建立的路徑 13 14 CGPathRelease(path);// 方式一 15 //CFRelease(path);// 方式二 16 }
它和下方程式碼是等價的,其實這種程式碼的閱讀性不好,更不便於區分
1 - (void)drawRect:(CGRect)rect{ 2 3 CGContextRef ctx=UIGraphicsGetCurrentContext(); 4 CGContextMoveToPoint(ctx, 20, 20); 5 CGContextAddLineToPoint(ctx, 200, 300); 6 CGContextStrokePath(ctx); 7 }
② 使用 path,一個 path 就代表一條路徑,使用起來清晰明瞭,如果要在上下文中繪製多個圖形,建議使用 path
1 - (void)drawRect:(CGRect)rect{ 2 3 CGContextRef ctx = UIGraphicsGetCurrentContext(); 4 5 // 直線 6 CGMutablePathRef path = CGPathCreateMutable(); 7 CGPathMoveToPoint(path, NULL, 20, 20); 8 CGPathAddLineToPoint(path, NULL, 200, 300); 9 CGContextAddPath(ctx, path); 10 11 // 圓 12 CGMutablePathRef path1 = CGPathCreateMutable(); 13 CGPathAddEllipseInRect(path1, NULL, CGRectMake(50, 50, 100, 100)); 14 CGContextAddPath(ctx, path1); 15 CGContextStrokePath(ctx); 16 17 // 釋放 18 CGPathRelease(path); 19 CFRelease(path1);// 屬於更底層的 cocafoundation框架 20 }
執行效果