Core Graphics 101: 線,矩形和漸變效果

Core Graphics 是iOS上一個很酷的API。作爲開發者,你會使用它來個性化你的UI設計,用上一些很棒的效果 – 而且不需要一個設計師參與制作!  但是對於很多iOS開發者來說,Core Graphics 一開始會讓人恐懼,因爲它是一個很龐大的API,而且在開發過程中會遇到很多小困難。所以在本篇教程中,我們將解開Core Graphics的神祕面紗,並且用一系列的練習去一步步展示 – 現在我們從使用Core Graphics來美化一個table view開始吧!

我們將要製作的table view的效果和上面的截屏一樣。這麼特別的設計靈感來自Bills,一個由 PoweryBase開發的設計精美的app。這是個相當酷的app,你可以看下!在該教程系列的第一篇中,我們將使用Core Graphics去製作一個精美的table view cell。

我們將講解Core Graphics的入門知識,如何去填充繪製矩形,如何繪製顏色漸變效果,以及如何處理1像素寬的線問題。

在接下來的教程系列中,將繼續美化app – Table view的header,footer和收尾工作。現在讓我們開始接觸有趣的Core Graphics吧!

開始

開始前,讓我們先創建一個有table view模版的工程。

打開Xcode,選擇Navigation-based Application 模版,命名工程爲 “CoolTable”。編譯運行工程,確保一個空白的table view出現。

Blank Table View

現在讓我們添加一些例子數據到table view中。打開RootViewController.h文件,根據以下內容做代碼修改:

// Inside @interface
NSMutableArray *_thingsToLearn;
NSMutableArray *_thingsLearned;
// After
@property (copy) NSMutableArray *thingsToLearn;
@property (copy) NSMutableArray *thingsLearned;

我們在這裏添加了兩個數組,在接下來爲兩個table view section中的內容添加字符串。現在切換到RootViewController.m文件並根據以下內容做修改:

// After @implementation
@synthesize thingsToLearn = _thingsToLearn;
@synthesize thingsLearned = _thingsLearned;
// Uncomment viewDidLoad and add the following:
self.title = @"Core Graphics 101";
self.thingsToLearn = [NSMutableArray arrayWithObjects:@"Drawing Rects", 
    @"Drawing Gradients", @"Drawing Arcs", nil];
self.thingsLearned = [NSMutableArray arrayWithObjects:@"Table Views", 
    @"UIKit", @"Objective-C", nil];
// Uncomment shouldAutorotateToInterfaceOrientation and change the return statement to the following:
return YES;
// Change the return value of numberOfSectionsInTableView to:
return 2;
// Change the return value of tableView:numberOfRowsInSection to:
if (section == 0) {
    return _thingsToLearn.count;
} else {
    return _thingsLearned.count;
}
// Inside tableView:cellForRowAtIndexPath, after the comment "Configure the cell":
NSString *entry;
if (indexPath.section == 0) {
    entry = [_thingsToLearn objectAtIndex:indexPath.row];
} else {
    entry = [_thingsLearned objectAtIndex:indexPath.row];
}        
cell.textLabel.text = entry;
// Inside viewDidUnload
self.thingsToLearn = nil;
self.thingsLearned = nil;
// Inside dealloc
[_thingsToLearn release];
_thingsToLearn = nil;
[_thingsLearned release];
_thingsLearned = nil;
// Add the following new method
-(NSString *) tableView:(UITableView *)tableView 
    titleForHeaderInSection:(NSInteger)section {
    if (section == 0) {
        return @"Things We'll Learn";
    } else {
        return @"Things Already Covered";
    }
}

我們在這裏添加了兩個數組,接下來會爲兩個table view section中的內容添加字符串。現在切換到RootViewController.m文件並根據以下內容做修改:

Table View with Plain Style

很好 – 現在我們有一些例子數據了!編譯運行工程,你將看到以下畫面:

然而,當你上下滾動table view的整個section內容時,header會“浮”在上面:

Table View with Plain Style - Floating Headers

這是一個標準的設定爲“plain”風格的table view行爲。然而,有了這個“plain”風格設定後,我們並不想讓header像這樣“浮”在上面 – 我們想讓它們像row(行)一樣是一個單元行。有“grouped”風格設定的table view就是我們想要的!

現在切換到RootViewController.xib文件,點擊xib中的Table View,設置Style參數爲“Grouped”:

Table View Style Setting

很好!保存RootViewController.xib的設定,返回工程,現在我們會看到一個有很多內容項(只是看起來)的table view:

Table View with Grouped Style

我們使用Core Graphics去美化它吧!但是在之前,我們還需要討論下我們想要的效果。

Table View 風格分析

爲了獲得我們想要的效果,我們將在table view的三個不同section中繪製:table header,cells和footer:

Table View Analyzed

在本篇教程中,我們將開始繪製cells,現在在讓我們進一步看下想要的效果:

Table View Cells Zoomed

注意以下對上述效果圖的分析:

  • cells具有從白色漸變到淺灰色的效果。
  • 每一個cell的邊界周圍都有白色邊來突出它(除了最後的cell,只在兩邊有白色邊)。
  • 每一個cell之間都有一條灰色的線來分隔它們(出了最後的cell)。
  • 頁面在實際的cell邊界處會呈現出一點鋸齒狀,和來自header的“下襬”頁面對齊。

另外 – 它模擬的是當有光線以一定角度照射在iPhone頂部時的情形(一般房間裏面會有光線的)。要達到這種效果,頂部需要提高亮度(白色),底部需要有陰影(灰色)。你會在很多UI設計裏面看到這種效果,接下來的教程系列裏面也會看到!

所以要繪製cell,我們需要知道如何使用Core Graphics去繪製漸變效果和一些線條。應該會相當簡單,對吧?我們開始吧!

你好,Core Graphics!

無論什麼時候你想在iOS上做個性化繪製,你繪製的代碼需要放在UIView內部。有一個特殊的方法叫drawRect,你可以把所有的繪製代碼都放到裏面。

我們先創建一個“Hello,World”的紅色view,然後把它設置爲table view cell 的背景,確保正常運作。

現在先選擇 “Groups & Files”下面的”Classes”分組,前往菜單欄的”FileNew File…”,選擇 iOSCocoa Touch Class,Objective-C class,確保”Subclass of UIView”被選上,然後點擊下一步。

命名文件爲 “CustomCellBackground.m”,確保”Also create CustomCellBackground.h”被選中,然後點擊 “Finish”。

我們不需要對頭文件做修改,直接切換到CustomCellBackground.m文件,根據以下代碼做出修改:

// Uncomment drawRect and replace the contents with the following:
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef redColor = 
    [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0].CGColor;
 
CGContextSetFillColorWithColor(context, redColor);
CGContextFillRect(context, self.bounds);

好的,這裏有一些新內容,先來一點點解釋下。

在第一行,我們調用了叫做UIGraphicsGetCurrentContext()的方法來獲得Core Graphics Context,在接下來的方法中還會用到它。按照我的理解,context就是我們在上面繪製的“畫布”。

按照這種情況,我們的“畫布”就是view,但是你也可以獲得其他類型的context,比如一個屏幕以外的緩衝區,你可以在稍後把它轉變成圖像。

關於context有趣的一點是,他們是狀態性的。這表示當你調用了函數去改變一些屬性,比如改變填充顏色,填充顏色會一直維持那個顏色狀態,直到你改變了顏色爲止。

事實上,這就是我們在第三行代碼所作的 – 我們用CGContextSetFillColorWithColor函數去把填充顏色設置爲紅色,以供接下來填充形狀顏色的時候使用。

你也許會注意到當你調用這個方法時,我們不能提供UIColor給函數做參數  – 而是要使用CGColorRef。幸運的是,其實很容易把UIColor轉換成CGColor,只需訪問UIColor的CGColor屬性。

最後的一行代碼,我們調用了一個方法去用顏色填充提供的方框(使用之前在context中設定好的填充顏色)。對於方框,我們傳入了view的bounds值。

既然我們有一個紅色view了,讓我們把它設置爲table view cell的背景吧!根據以下代碼對RootViewController.m文件做修改:

// At top of file
#import "CustomCellBackground.h"
// Inside RootViewController.m, in the tableView:cellForRowAtIndexPath method, 
//   inside the cell == nil case, after the call to initWithStyle:
cell.backgroundView = [[[CustomCellBackground alloc] init] autorelease];
cell.selectedBackgroundView = [[[CustomCellBackground alloc] init] autorelease];
// At end of function, right before return cell:
cell.textLabel.backgroundColor = [UIColor clearColor];

我們在這裏做的是將每個cell的backgroundView和selectedBackgroundView在新的CustomCellBackground類中創建。我們還把cell的文本標籤text label的背景顏色設定爲clear,讓我們的背景可以顯露出來。

編譯運行程序,你將看到以下畫面:

Hello, Core Graphics!

太好了,我們可以用Core Graphics去繪製了!不管你信不信,我們已經學會了一些重要的技術 – 如何獲取一個context去繪製,如何改變填充顏色,如何用顏色去填充方框。你可以用這種方法去製作精美的UI了!

現在我們要進一步深入,學習其中一種最有用的技術去製作精美的UI:顏色漸變!

繪製漸變效果

我們將要在本工程中繪製很多漸變效果,所以讓我們添加顏色漸變代碼到一個輔助函數中。這樣我們就不需要在工程中重複編寫這部分的代碼了!確保你選中了”Groups & Files”下面的”Classes”分組,前往菜單項的”FileNew File…”,選擇 iOSCocoa Touch Class,Objective-C class,確保”Subclass of NSObject”選項被選中,然後點擊Next,命名文件爲“Common.m”,確保”Also create Common.h”選項被選中,然後點擊”Finish”。

現在使用以下代碼替換掉Common.h文件的內容:

#import <Foundation/Foundation.h>
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, 
    CGColorRef  endColor);

我們在這裏不是定義一個類 – 我們只是定義一個公共方法。

現在切換到Common.m文件,使用以下代碼替換掉原來的內容:

#import "Common.h"
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, 
    CGColorRef  endColor) {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat locations[] = { 0.0, 1.0 };
 
    NSArray *colors = [NSArray arrayWithObjects:(id)startColor, (id)endColor, nil];
 
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, 
        (CFArrayRef) colors, locations);
 
    // More coming... 
}

這個函數裏面有很多技術點,現在分兩部分去解釋。我們先從剛纔寫的部分開始,它創建了接下來要繪製的漸變效果。

首先我們需要去做的是獲得將要繪製的漸變效果的color space。你可以用color space去做很多事情,但是99%的時間你只想要一個標準的基於設備的RGB color space,所以我們簡單的使用了CGColorSpaceCreateDeviceRGB函數去獲取需要的引用。

接下來,我們創建了一個數組去記錄漸變區域的每一種顏色。0數值可以表示漸變的開始,1表示漸變的結束。我們只有兩種顏色,然後我們想用第一種顏色作爲開始,第二種顏色作爲結束,所以傳入了0和1數組。

注意到你可以在顏色漸變中有三種甚至多種你想要的顏色,還可以設定哪種顏色會在漸變這裏開始。這在一些效果中將會很有用。之後,我們用傳入函數中的顏色去創建一個數組。在這裏爲了方便,我們使用了普通的NSArray。

我們接着用CGGradientCreateWithColors函數創建了漸變效果,傳入了顏色空間,顏色數組,還有之前定義的位置信息。注意到我們必須轉換NSArray爲一個CFArrayRef – 這相當簡單,我們可以用casting方式去做。

起作用的原因是因爲NSArray是CGArrayRef的“toll-free bridged” – 基本上是一種奇特的稱呼方式,Apple寫了所有魔法般的代碼去讓轉換像casting那樣簡單。

現在我們有一個漸變 的引用了,但是它還沒有繪製出任何東西 – 它只是一個指向我們稍後用來繪製它的信息的指針而已。現在我們開始動手吧!在drawLinearGradient方法的“More coming”註釋後面添加以下代碼:

CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));

CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
 
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
 
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);

首先我們要計算出我們要繪製的漸變效果的開始和結束點。我們從矩形的”頂部中間”到“底部中間”設置一條線。注意到這裏使用了來自CGGeometry.h的一些輔助函數(比如 CGRectGetMidX)去計算這些數據(可以讓我們的代碼更簡潔!)。

剩下的代碼幫助我們去在提供的矩形中繪製漸變 效果- 關鍵的函數是 CGContextDrawLinearGradient。關於這個函數比較詭異的一點是,它用漸變填充了整個繪製區域。沒有辦法讓漸變只填充在部分區域中。

好的…沒有了裁剪,就是這樣!裁剪是Core Graphics的一項出色的功能特性,讓你可以在任意形狀中限制繪製操作。你需要做的就是添加形狀到context上面,然後調用CGContextClip方法,而不是像之前那樣填充它。以後的繪製動作都會被限定在那個區域中!

這就是我們在這裏要做的。我們把矩形添加到context上面,裁剪它,然後調用CGContextDrawLinearGradient方法,傳入之前設定好的所有變量值。

CGContextSaveCGState/CGContextRestoreCGState是什麼呢?Core Graphics是一個狀態機,一旦你設定了一些操作,需要你修改它才能改變狀態。

好的,我們只是裁剪了一個區域,除非我們對裁剪區域做了修改,不然我們都不會在該區域範圍之外繪製了!

這就是 CGContextSaveCGState/CGContextRestoreCGState的用處。使用它,我們可以保存當前的context設置到棧中,然後當我們完成操作,回到之前的狀態時,讓它出棧即可。

最後需要做的 – 我們需要調用 CGGradientRelease方法去清空CGGradientCreateWithColor方法之前創建的內存空間(還有CGColorSpaceRelease方法,感謝@Jim!)。

就是這樣!讓我們在cell的背景中用上這個函數吧。打開CustomCellBackground.m文件,根據以下內容做修改:

// Add to top of file
#import "Common.h"
// Replace contents of drawRect with the following:
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef whiteColor = [UIColor colorWithRed:1.0 green:1.0 
    blue:1.0 alpha:1.0].CGColor; 
CGColorRef lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 
    blue:230.0/255.0 alpha:1.0].CGColor;
CGRect paperRect = self.bounds;
drawLinearGradient(context, paperRect, whiteColor, lightGrayColor);

編譯運行工程,你將看到以下畫面:

Cells with Gradients

wow,簡單的顏色漸變做出來的效果真不錯!

繪製軌跡

到現在爲止,table view看起來挺不錯的了,但是我們還是要繼續做些修改,讓它稍微“突出”一點。我們會在邊界周圍繪製一個白色的矩形,還有cell之間的灰色分隔線。

我們已經知道如何去給矩形填充顏色了 – 同樣的,在方框周圍繪製線條同樣簡單!

根據以下代碼,對CustomCellBackground.m文件進行修改:

// Add a color for red up where the colors are
CGColorRef redColor = [UIColor colorWithRed:1.0 green:0.0 
    blue:0.0 alpha:1.0].CGColor;
// Add down at the bottom
CGRect strokeRect = CGRectInset(paperRect, 5.0, 5.0);
CGContextSetStrokeColorWithColor(context, redColor);
CGContextSetLineWidth(context, 1.0);
CGContextStrokeRect(context, strokeRect);

我們將要用紅色的線去繪製矩形,並把它放置在cell的中間,先讓它容易被看到。我們創建一種顏色,然後使用CGRectInset函數稍微縮小方框的尺寸。

CGRectInset方法要做的就是從方框的寬和高減少一定值,然後返回結果。

我們再把繪製顏色設置爲紅色,設置線的寬度爲一像素寬,然後調用CGContextStrokeRect方法去繪製矩形。

編譯運行工程,你將看到以下畫面:

Fuzzy 1 Pixel Lines in Core Graphics

看起來似乎還OK… 但是否會覺得有點模糊和奇怪?如果你放大它,你將看到一些古怪的現象:

Fuzzy 1 Pixel Lines in Core Graphics - Zoomed

我們用1像素點去繪製(與iPhone3GS的1像素點一樣),但是事實上它卻用幾個像素點去繪製… 怎麼會這樣子?

像素點線和像素邊界線

當你使用Core Graphics去繪製路徑時,它會剛好在軌跡邊的中間繪製。

我們的情況是,軌跡的邊是我們想要去填充的矩形。所以當我們沿着邊繪製1像素點的線時,有一半的線(1/2像素)會在矩形的內部,另一半線(1/2像素)會在矩形的外部。

當然,因爲沒有辦法去繪製1/2一個像素,Core Graphics使用了圖像保真的方法把兩個像素點吸到一起,但是有一個較淡的陰影讓它的外表看起來像只繪製了一個像素點。

但是我們不想要圖像保真,我們只想要一個像素點!這裏有幾種方式去修復它:

  • 你可以使用裁剪去裁掉不想要的像素
  • 你可以取消圖像保真並且修改矩形的邊界,確保是你想要繪製的線條。
  • 你可以修改軌跡去繪製,這樣1/2像素的效果就可以考慮了

在本篇教程中,我們會用option #3,修改矩形,讓它具有筆畫的行爲。我們創建一個輔助函數去修改一個矩形爲1像素點筆畫。

打開Common.h文件,然後添加以下聲明到文件的底部:

CGRect rectFor1PxStroke(CGRect rect);

添加以下代碼到Common.m文件中:

CGRect rectFor1PxStroke(CGRect rect) {
    return CGRectMake(rect.origin.x + 0.5, rect.origin.y + 0.5, 
        rect.size.width - 1, rect.size.height - 1);
}

這裏我們修改了矩形,讓一半邊界進入到原來矩形的像素點中,讓筆畫符合預期效果。

在 CustomCellBackground.m文件中調用以下代碼:

// Replace strokeRect declaration with the following:
CGRect strokeRect = rectFor1PxStroke(CGRectInset(paperRect, 5.0, 5.0));

現在如果你編譯運行工程,矩形的邊看起來很好很醒目:
1 Pixel Lines in Core Graphics - Sharp

很好。現在讓我們用正確的顏色和位置去完善它。使用以下代碼對 CustomCellBackground.m文件進行修改:

// Replace strokeRect declaration and setting stroke color with the following:
CGRect strokeRect = paperRect;
strokeRect.size.height -= 1;
strokeRect = rectFor1PxStroke(strokeRect);
CGContextSetStrokeColorWithColor(context, whiteColor);

這裏我們減小了1像素點的頁面框高度,以便有空間可以放置分隔線,轉換它,使用白色去筆畫繪製。

編譯運行工程,現在應該有一條微小的白色邊界在cell的周圍。
Custom Cells with White Border

接下來,我們要在cell之間添加淺灰色的分隔線!

繪製線條

因爲我們要在工程中繪製多條線,讓我們創建一個輔助函數吧。添加以下代碼到Common.h文件中:

void draw1PxStroke(CGContextRef context, CGPoint startPoint, CGPoint endPoint, 
    CGColorRef color);

把以下代碼添加到Common.m文件中:

void draw1PxStroke(CGContextRef context, CGPoint startPoint, CGPoint endPoint, 
    CGColorRef color) {
 
    CGContextSaveGState(context);
    CGContextSetLineCap(context, kCGLineCapSquare);
    CGContextSetStrokeColorWithColor(context, color);
    CGContextSetLineWidth(context, 1.0);
    CGContextMoveToPoint(context, startPoint.x + 0.5, startPoint.y + 0.5);
    CGContextAddLineToPoint(context, endPoint.x + 0.5, endPoint.y + 0.5);
    CGContextStrokePath(context);
    CGContextRestoreGState(context);        
 
}

好的,我們看下它的原理。在開始和結尾,我們保存和恢復了context,這樣我們就不會留下任何的更改操作。

然後設定線條的線帽。默認的設定是讓一條線有一個“圓”末端,表示線條剛好在最後的點處結束。

但是這個還不夠好,因爲我們要讓線以1/2點的長度從開始和結束位置縮進,來修正筆畫問題。所以我們讓線帽有一個“正方形”的末端,表示線條在末端伸長了1/2的線寬 – 就我們的1/2點情況而言 – 太完美了!

然後按照通常那樣設定顏色和線條寬度。

接着我們做線條的實際繪製。在Core Graphics中繪製線條,你首先要移動到點A(還沒有繪製任何東西),然後添加一條線到點B(在context中從點A添加一條線到點B)。你可以調用CGContextStrokePath方法去繪製線條。

就這樣!讓我們用它來繪製分隔線,添加以下代碼到 CustomCellBackground.m文件的drawRect方法中:

// Add in color section
CGColorRef separatorColor = [UIColor colorWithRed:208.0/255.0 green:208.0/255.0 
    blue:208.0/255.0 alpha:1.0].CGColor;
// Add at bottom
CGPoint startPoint = CGPointMake(paperRect.origin.x, 
    paperRect.origin.y + paperRect.size.height - 1);
CGPoint endPoint = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, 
    paperRect.origin.y + paperRect.size.height - 1);
draw1PxStroke(context, startPoint, endPoint, separatorColor);

編譯運行工程,現在你在cell之間應該可以看到漂亮的分隔線了!
Custom Cells with Separator

現在可以做什麼?

這個是上面的工程項目源代碼,你可以在這裏下載

到現在你應該對Core Graphics又酷又強大的技術相當熟悉了 – 填充和繪製矩形, 繪製線條和漸變效果,還有裁剪軌跡!我們的table view看起來也挺酷的。

還有更多內容!我們還沒講解如何添加陰影效果,或者弧線,光澤效果,還有其他很酷的技術 – 在下一篇教程中,我們將會添加一個很酷的header到table view上!

同時,如果你有任何的問題,建議或者評論,請提出來!:]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章