Core Graphics 101: 线,矩形和渐变效果

来源:互联网 发布:华安股票交易软件 编辑:程序博客网 时间:2024/05/01 14:21

原文地址:http://www.raywenderlich.com/zh-hans/20461/core-graphics-101-%E7%BA%BF%EF%BC%8C%E7%9F%A9%E5%BD%A2%E5%92%8C%E6%B8%90%E5%8F%98%E6%95%88%E6%9E%9C

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 @interfaceNSMutableArray *_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 viewDidUnloadself.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 areCGColorRef redColor = [UIColor colorWithRed:1.0 green:0.0     blue:0.0 alpha:1.0].CGColor;// Add down at the bottomCGRect 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 sectionCGColorRef separatorColor = [UIColor colorWithRed:208.0/255.0 green:208.0/255.0     blue:208.0/255.0 alpha:1.0].CGColor;// Add at bottomCGPoint 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上!

同时,如果你有任何的问题,建议或者评论,请提出来!:]


原创粉丝点击