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

原文地址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


这篇文章还可以在这里找到 英语日语俄语

If you're new here, you may want to subscribe to my RSS feed or follow me on Twitter. Thanks for visiting!

Welcome to Core Graphics 101!

欢迎来到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上!

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值