上下文棧
一、qurza2d是怎麼將繪圖資訊和繪圖的屬性繪製到圖形上下文中去的?
說明:
建立一個項目,自訂一個view類和storyboard關聯後,重寫該類中的drowrect方法。
畫線的三個步驟:
(1)擷取上下文
(2)繪圖
(3)渲染
要求:畫兩條單獨的線
代碼和效果圖:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//渲染
CGContextStrokePath(ctx);
}
效果圖:
設定線段的寬度:兩頭為圓形,顏色等。
代碼和效果圖(發現第二條線也被渲染成第一條線的樣式和狀態)
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//設定第一條線的狀態
//設定線條的寬度
CGContextSetLineWidth(ctx, 12);
//設定線條的顏色
[[UIColor brownColor]set];
//設定線條兩端的樣式為圓角
CGContextSetLineCap(ctx,kCGLineCapRound);
//對線條進行渲染
CGContextStrokePath(ctx);
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//渲染
CGContextStrokePath(ctx);
}
效果圖:
新的需求:要讓兩條線的顏色不一樣,要求第二條線變成原版的樣子。要達到上面的要求,有以下幾種做法:
第一種做法:
在對第二條線進行設定的時候,清空它的狀態
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//設定第一條線的狀態
//設定線條的寬度
CGContextSetLineWidth(ctx, 12);
//設定線條的顏色
[[UIColor brownColor]set];
//設定線條兩端的樣式為圓角
CGContextSetLineCap(ctx,kCGLineCapRound);
//對線條進行渲染
CGContextStrokePath(ctx);
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//清空狀態
CGContextSetLineWidth(ctx, 1);
[[UIColor blackColor]set];
CGContextSetLineCap(ctx,kCGLineCapButt);
//渲染
CGContextStrokePath(ctx);
}
第二種做法:
把第一條線從開始繪製到渲染的代碼剪下到第二條線渲染完成之後,這樣先繪製並渲染了第一條線,該線並沒有對繪製資訊進行過設定,顯示出來的第二條線即位系統預設的效果。
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//清空狀態
// CGContextSetLineWidth(ctx, 1);
// [[UIColor blackColor]set];
// CGContextSetLineCap(ctx,kCGLineCapButt);
//渲染
CGContextStrokePath(ctx);
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//設定第一條線的狀態
//設定線條的寬度
CGContextSetLineWidth(ctx, 12);
//設定線條的顏色
[[UIColor brownColor]set];
//設定線條兩端的樣式為圓角
CGContextSetLineCap(ctx,kCGLineCapRound);
//對線條進行渲染
CGContextStrokePath(ctx);
}
兩種方式完成的效果相同:
但是有的情況下,必須要先畫第一條線再畫第二條線,要求在交叉部分,第二條線蓋在第一條線的上面。如果要求是這樣,那麼只能使用第一種做法,但是如果現在有新的需求,要求在這個基礎上再畫兩條線,那就需要清空ctx中的狀態很多次,很麻煩。為瞭解決這個問題,下面給大家介紹圖形上下文棧。
二、繪圖的完整過程
程式啟動,顯示自訂的view。當程式第一次顯示在我們眼前的時候,程式會調用drawRect:方法,在裡面擷取了圖形上下文(在記憶體中擁有了),然後利用圖形上下文儲存繪圖資訊,可以理解為圖形上下文中有一塊地區用來儲存繪圖資訊,有一塊地區用來儲存繪圖的狀態(線寬,圓角,顏色)。直線不是直接繪製到view上的,可以理解為在圖形上下文中有一塊單獨的地區用來先繪製圖形,當調用渲染方法的時候,再把繪製好的圖形顯示到view上去。
在繪製繪圖區域,會去儲存繪圖狀態區域中尋找對應的狀態資訊(線寬,圓角,顏色),然後在繪圖區域把對第一條直線繪製完成。其實在渲染之前,就已經把直線在繪製繪圖區域畫好了。
如圖:
說明:這些示意圖和本文中的程式碼塊,不具備一一對應關係,只是為了說明繪圖的完整過程。
調用渲染方法的時候,把繪製繪圖區域已經畫好的圖形直接顯示到view上,就是我們看到的樣子了。
如圖:
畫第二條的時候,如果沒有對繪圖狀態進行重新設定,那麼可以發現畫第一天線的時候使用的繪圖狀態還儲存在圖形上下文中,在第二條線進行渲染之前,會根據第一條線(上一份繪圖狀態)對第二條線進行相應的設定,渲染後把第二條線顯示到螢幕上。
參考代碼:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//設定第一條線的狀態
//設定線條的寬度
CGContextSetLineWidth(ctx, 12);
//設定線條的顏色
[[UIColor brownColor]set];
//設定線條兩端的樣式為圓角
CGContextSetLineCap(ctx,kCGLineCapRound);
//對線條進行渲染
CGContextStrokePath(ctx);
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//渲染
CGContextStrokePath(ctx);
}
如果清空了狀態,則在渲染之前,在繪製繪圖區域對第二條線進行繪製的時候,會去尋找當前的繪圖資訊(已經更改——清空),根據繪圖資訊對第二條線進行繪製,調用渲染方法的時候把第二條線顯示到view上。
參考代碼:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//設定第一條線的狀態
//設定線條的寬度
CGContextSetLineWidth(ctx, 12);
//設定線條的顏色
[[UIColor brownColor]set];
//設定線條兩端的樣式為圓角
CGContextSetLineCap(ctx,kCGLineCapRound);
//對線條進行渲染
CGContextStrokePath(ctx);
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//清空狀態
CGContextSetLineWidth(ctx, 1);
[[UIColor blackColor]set];
CGContextSetLineCap(ctx,kCGLineCapButt);
//渲染
CGContextStrokePath(ctx);
}
三、圖形上下文棧
1.簡單說明
在擷取圖形上下文之後,通過 CGContextSaveGState(ctx); 方法,把當前擷取的上下文拷貝一份,儲存一份最純潔的圖形上下文。
在畫第二條線之前,使用CGContextRestoreGState(ctx);方法,還原開始的時候儲存的那份最純潔的圖形上下文。
代碼:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//儲存一份最初的圖形上下文
CGContextSaveGState(ctx);
//繪圖
//第一條線
CGContextMoveToPoint(ctx, 20, 100);
CGContextAddLineToPoint(ctx, 100, 320);
//設定第一條線的狀態
//設定線條的寬度
CGContextSetLineWidth(ctx, 12);
//設定線條的顏色
[[UIColor brownColor]set];
//設定線條兩端的樣式為圓角
CGContextSetLineCap(ctx,kCGLineCapRound);
//對線條進行渲染
CGContextStrokePath(ctx);
//還原開始的時候儲存的那份最純潔的圖形上下文
CGContextRestoreGState(ctx);
//第二條線
CGContextMoveToPoint(ctx, 40, 200);
CGContextAddLineToPoint(ctx, 80, 100);
//清空狀態
// CGContextSetLineWidth(ctx, 1);
// [[UIColor blackColor]set];
// CGContextSetLineCap(ctx,kCGLineCapButt);
//渲染
CGContextStrokePath(ctx);
}
2.圖形上下文棧機制
畫第一條線的時候,會把當前的圖形上下文拷貝一份儲存到圖形上下文棧中。
畫第二條線的時候,去圖形上下文棧中取出棧頂的繪圖資訊,作為第二條線的狀態資訊,第二條線的狀態資訊也是據此(最初儲存的那份圖形上下文)進行繪製。
注意:在棧裡儲存了幾次,那麼就可以取幾次(比如不能儲存了1次,取兩次,在取第二次的時候,棧裡為空白會直接掛掉)。
矩陣操作
一、關於矩陣操作
1.畫一個四邊形
通過設定兩個端點(長和寬)來完成一個四邊形的繪製。
代碼:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//畫四邊形
//擷取圖形上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//繪圖
CGContextAddRect(ctx, CGRectMake(20, 50, 100, 100));
//渲染
CGContextStrokePath(ctx);
}
說明:通過這種方式畫矩形有弱點:畫出來的矩形永遠都是正的。如下圖:
2.畫一個歪的四邊形
如何畫一個歪的矩形?(通過矩陣操作來完成,和形變操作相似)
可以通過矩陣操作,把畫出來的東西進行形變(旋轉,縮放,平移)
方法:CGContextRotateCTM(<#CGContextRef c#>, <#CGFloat angle#>)該接受兩個參數(圖形上下文,弧度)
注意點:設定矩陣操作必須要在添加圖形之前,如果設定在添加圖形之後的話,此時它已經畫完了,無效。
代碼:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//畫四邊形
//擷取圖形上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//矩陣操作
//注意點:設定矩陣操作必須要在添加繪圖資訊之前
//旋轉45度
CGContextRotateCTM(ctx, M_PI_4);
//繪圖
CGContextAddRect(ctx, CGRectMake(150, 100, 100, 100));
//渲染
CGContextStrokePath(ctx);
}
效果:
二、關於旋轉
1.旋轉示範
view之所以能夠顯示視圖,是因為它的上面有layer,將來圖形也是渲染到layer上面。
且,旋轉的時候是整個layer都旋轉了,可以再畫一個圓進行驗證。
代碼1(未旋轉):
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取圖形上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//矩陣操作
//注意點:設定矩陣操作必須要在添加繪圖資訊之前
//旋轉45度
// CGContextRotateCTM(ctx, M_PI_4);
//繪圖
//畫四邊形
CGContextAddRect(ctx, CGRectMake(150, 100, 100, 100));
//畫一個圓
CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
//渲染
CGContextStrokePath(ctx);
}
效果:
代碼2(旋轉):
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取圖形上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//矩陣操作
//注意點:設定矩陣操作必須要在添加繪圖資訊之前
//旋轉45度
CGContextRotateCTM(ctx, M_PI_4);
//繪圖
//畫四邊形
CGContextAddRect(ctx, CGRectMake(150, 100, 100, 100));
//畫一個圓
CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
//渲染
CGContextStrokePath(ctx);
}
效果:
2.關於旋轉的補充說明
提示:旋轉的時候,是整個layer都旋轉了。
三、縮放
方法:CGContextScaleCTM(<#CGContextRef c#>, <#CGFloat sx#>, <#CGFloat sy#>)
該方法接收三個參數(圖形上下文,x方向的縮放比例,y方向上的縮放比例
程式碼範例:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取圖形上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//矩陣操作
//注意點:設定矩陣操作必須要在添加繪圖資訊之前
//縮放,x方向縮放0.5倍,y方向縮放1.5倍
CGContextScaleCTM(ctx, 0.5, 1.5);
//繪圖
//畫四邊形
CGContextAddRect(ctx, CGRectMake(150, 100, 100, 100));
//畫一個圓
CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
//渲染
CGContextStrokePath(ctx);
}
效果:
四、平移
方法: CGContextTranslateCTM(<#CGContextRef c#>, <#CGFloat tx#>, <#CGFloat ty#>)
該方法接收三個參數(圖形上下文,x方向的位移量,y方向上的位移量)
程式碼範例:
複製代碼 代碼如下:
- (void)drawRect:(CGRect)rect
{
//擷取圖形上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//矩陣操作
//注意點:設定矩陣操作必須要在添加繪圖資訊之前
//平移,x方向移動50,y方向移動100
CGContextTranslateCTM(ctx, 50, 100);
//繪圖
//畫四邊形
CGContextAddRect(ctx, CGRectMake(150, 100, 100, 100));
//畫一個圓
CGContextAddEllipseInRect(ctx, CGRectMake(200, 200, 50, 50));
//渲染
CGContextStrokePath(ctx);
}
效果:
提示:座標原點為view的左上方。