图形与动画(三)--附Demo
最近在看《iOS 6 Programming Cookbook》的翻译版,它由DevDiv论坛的网友翻译,原文地址:点击跳转。由于下载的pdf都有水印,并且排版不是很好,特别是代码排版,基本不能看,所以这里就整理了一下,方便再次查看。另外把里面提到的点写了一个demo,由于里面一些代码现在已经废弃,所以demo中都是用的新api,下载地址在这里:图形与动画Demo。
1.8 为形状增加阴影
使用 CGContextSetShadow
。用core graphic
s绘制阴影很容易。图形环境是容纳阴影的元素。就是说,你要先对环境应用阴影,再绘制需要阴影的形状,最后要将阴影从环境上移除(或者设置一个新环境)。
在 core graphics
中,我们可以使用下列两个过程来为图形环境应用阴影:
CGContextSetShadow 过程
这个过程会创建黑色或灰色的阴影,它接受三个参数:
- 要使用阴影的图形环境;
- 阴影的位移,由 CGSize 类型值指定,从每个形状要应用阴影的右下部分开始。位移 的 x 值越大,形状右边的阴影就扩散得越远。位移的 y 值越大,下部的阴影就越低;
- 阴影的模糊值,以浮点值(CGFloat)来指定。指定 0.0f 将导致阴影成为固态形状。这个 值越高,阴影就越模糊。
CGContextSetShadowWithColor
这个方法接受的参数和 CGContextSetShadow
完全相同,不过加了一个 CGColorRef 类型的参数,用于设置阴影的颜色。
在此前我 到,图形环境会保留阴影属性,直到我们显式的移除阴影。我们看一个例子来让这一点更清楚吧。下面我们编写代码来绘制两个矩形,第一个有阴影,第二个没有。我们这样绘制第一个矩形:
- (void) drawRectAtTopOfScreen
{
/* Get the handle to the current context */
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor(currentContext, CGSizeMake(10.0f, 10.0f), 20.0f, [[UIColor grayColor] CGColor]);
/* Create the path first. Just the path handle. */
CGMutablePathRef path = CGPathCreateMutable();
/* Here are the rectangle boundaries */
CGRect firstRect = CGRectMake(55.0f, 60.0f, 150.0f, 150.0f);
/* Add the rectangle to the path */
CGPathAddRect(path,NULL,firstRect);
/* Add the path to the context */
CGContextAddPath(currentContext, path);
/* Set the fill color to cornflower blue */
[[UIColor colorWithRed:0.20f green:0.60f blue:0.80f alpha:1.0f] setFill];
/* Fill the path on the context */
CGContextDrawPath(currentContext, kCGPathFill);
/* Dispose of the path */
CGPathRelease(path);
}
在视图对象的 drawRect:方法里调用上面的方法,我们会看到屏幕上画出了一 个带有漂亮阴影的矩形,如下图:
图8-1 对矩形使用阴影下面我们画第二个矩形,我们没有要求有阴影,但是我们保持了在绘制第一个矩形时图
形环境的阴影属性:
- (void) drawRectAtBottomOfScreen
{
/* Get the handle to the current context */
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGMutablePathRef secondPath = CGPathCreateMutable();
CGRect secondRect = CGRectMake(150.0f, 250.0f, 100.0f,100.0f);
CGPathAddRect(secondPath,NULL,secondRect);
CGContextAddPath(currentContext, secondPath);
[[UIColor purpleColor] setFill];
CGContextDrawPath(currentContext, kCGPathFill);
CGPathRelease(secondPath);
}
drawRect:
方法首先调用了 drawRectAtTopOfScreen
方法,此后,调用 drawRectAtBottomOfScreen
方法。我们在 drawRectAtBottomOfScreen
中没有要求一个阴影, 但是如果你跑一下程序,你会看到类似下图 所示的结果
你马上观察到屏幕下方的第二个矩形被应用了阴影效果。为了避免这个,我们将在对图
形环境应用阴影效果之前保存它的状态,并在我们想去掉阴影效果时,恢复之前的状态。
广而言之,图形环境状态的存取不仅限于阴影。恢复图形环境的状态会恢复一切(填充
色,字体,线宽,等等)到你之前的设置。所以如果你同时也设置了填充和画笔颜色,这些
颜色也会被重置。
你可以通过 CGContextSaveGState
过程来保存图形环境的状态,而通过 CGContextRestoreGState
过程恢复之前的状态。所以如果修改 drawRectAtTopOfScreen 过 程,在应用阴影前保存图形环境状态,并在绘制路径后恢复其状态,我们将得到不同的结 果,如下图 所示:
修改后的代码:
- (void) drawRectAtTopOfScreen
{
/* Get the handle to the current context */
CGContextRef currentContext = UIGraphicsGetCurrentContext();
//保存当前图形上下文状态
CGContextSaveGState(currentContext);
CGContextSetShadowWithColor(currentContext, CGSizeMake(10.0f, 10.0f), 20.0f, [[UIColor grayColor] CGColor]);
/* Create the path first. Just the path handle. */
CGMutablePathRef path = CGPathCreateMutable();
/* Here are the rectangle boundaries */
CGRect firstRect = CGRectMake(55.0f, 100.0f, 150.0f, 150.0f);
/* Add the rectangle to the path */
CGPathAddRect(path,NULL,firstRect);
/* Add the path to the context */
CGContextAddPath(currentContext, path);
/* Set the fill color to cornflower blue */
[[UIColor colorWithRed:0.20f green:0.60f blue:0.80f alpha:1.0f] setFill];
/* Fill the path on the context */
CGContextDrawPath(currentContext, kCGPathFill);
/* Dispose of the path */
CGPathRelease(path);
//恢复当前图形上下文状态
CGContextRestoreGState(currentContext);
}
1.9 绘制渐变
使用 CGGradientCreateWithColor
函数。
core graphics
允许程序员创建两类渐变:轴向的(axial)
和放射状的(radial)
。
这里只讨论轴向渐变。轴向渐变是以一种颜色为起点,而以另一种颜色为终点的渐变(虽然可以在起点和终点用一种颜色,但是那不会产生渐变)。“轴向”的意思是和某个轴有关。 两个点(起点和终点)形成一条线段,这就是渐变要被绘制的轴。
为了创建一个轴向渐变,你必须调用 CGGradientCreateWithColorComponents
函数。函数的返回值是一个 CGGradientRef
类型的渐变。它是渐变的句柄。当你用完渐变后,你必须调用 CGGradientRelease
释放掉。
CGGradientCreateWithColorComponents
函数接受四个参数:
- 颜色空间
这是一个颜色范围的容器,这个参数必须是CGColorSpaceRef
类型的,我们只要传入CGColorSpaceCreateDeviceRGB
函数的返回值即可,该函数会给我们一个 RGB 颜色空间。
- 颜色组件的数组(详情参见 1.2 小节)
这个数组必须包含红,绿,蓝和 alpha 值,都以 CGFloat 表示。数组中元素的个数和下面两个参数紧密相连。你必须在这个数组中包含足够的值来指定第四个参数中的位置数量。 所以,如果你请求两个位置(起点和终点),你就必须在数组中提供两种颜色。因为每种颜色都是由红、绿、蓝和 alpha 组成,这个数组必须有 2X4 项:两种颜色分别 4 个。
-
颜色数组中的颜色位置
这个参数控制了渐变过度的迅速程度。元素的个数一定要和第 4 个参数一样。如果我请求 4 个颜色,比如说,我们希望第 1 种颜色为起始色,最后一种颜色为终止色,这时我们就要提供包含两个 CGFloat 类型元素的数组,第一个元素设置为 0.0f(颜色数组的第一个),第二个元素设置为 3.0f(颜色数组的第 4 个)。中间的两个颜色的值决定了在起点和终点间如何插入颜色。 -
位置数量
指定我们想要有多少种颜色和位置。
我们来看例子。假设我们想绘制如图 图9-1 所示的渐变,下面是步骤:
- 选择渐变的开始和结束点 -- 它变换的轴。这里,我们选择了从左向右移动。想像你在沿着一条假想的水平线移动时改变颜色。沿着那条线,我们会传播这些颜色使得每条垂直于这条水平线的线上只包含一种颜色。这里,这些垂直的线将会是图 9-1 中的垂线。近距离观察那些垂直线,其中的每一条从上到下都只包含一种颜色。这就是轴向渐变的原理。
- 如前所述,现在我们要创建一个颜色空间来作为
CGGradientCreateWithColorComponents
函数的第一个参数:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
在我们用完这个颜色空间后,我们会释放它。
- 根据图 9-1 所选择的颜色,我们选择蓝色作为起点(左边),绿色作为终点(右边)。我选择的变量名(
startColorComponents
和endColorComponents
)是特意选定来帮助我们记住我们对每个颜色在做的事情。实际上我们会使用数组位置来指定起点和终点:
UIColor *startColor = [UIColor blueColor];
CGFloat *startColorComponents =
(CGFloat *)CGColorGetComponents([startColor CGColor]); UIColor *endColor = [UIColor greenColor];
CGFloat *endColorComponents =
(CGFloat *)CGColorGetComponents([endColor CGColor]);
- 在你获得每个颜色的组件后,我们将他们放到一个简单的数组中,以传入
CGGradientCreateWithColorComponents
函数:
CGFloat colorComponents[8] = {
// Four components of the blue color (RGBA)
startColorComponents[0],
startColorComponents[1],
startColorComponents[2],
startColorComponents[3], /* First color = blue */
//Four components of the green color (RGBA)
endColorComponents[0],
endColorComponents[1],
endColorComponents[2],
endColorComponents[3], /* Second color = green */
};
- 因为这个数组只有两种颜色,我们需要指定第一个是渐变的最开始(位置 0.0)而第二个在最后(位置 1.0)。让我们将这些索引放到数组中作为参数传递到
CGGradientCreateWithColorComponents
函数:
CGFloat colorIndices[2] = {
0.0f, /* Color 0 in the colorComponents array */
1.0f, /* Color 1 in the colorComponents array */
};
- 现在我们要做的只剩下用之前创建的参数实际调用
CGGradientCreateWithColorComponents
函数:
CGGradientRef gradient = CGGradientCreateWithColorComponents
(colorSpace,
(const CGFloat *)&colorComponents,
(const CGFloat *)&colorIndices,
2);
- 非常棒!现在我们在
gradient
变量中保存了我们的渐变对象。在我们忘记之前,还必须释放之前在CGColorSpaceCreateDeviceRGB
函数中创建的颜色空间:
CGColorSpaceRelease(colorSpace);
现在我们将使用 CGContextDrawLinearGradient 来往图形环境上面绘制轴向渐变。这个 过程接受 5 个参数:
- 图形环境
指定轴向渐变被绘制到的图形环境 - 轴向渐变
轴向渐变对象的句柄。我们在CGGradientCreateWithColorComponents
函数中创建这个 渐变对象。 - 起点
图形环境中的一点,由 CGPoint 指定,它指定了渐变的起点。 - 终点
图形环境中的一点,由 CGPoint 指定,它指定了渐变的终点。 - 渐变绘制选项
指定了当你指定的起点或终点不是图形环境的边缘时的行为。你可以使用你的开始色或
结束色来填充渐变之外的区域。指定下列参数值之一:
kCGGradientDrawsAfterEndLocation
在渐变终点之后将渐变扩展到所有点
kCGGradientDrawsBeforeStartLocation
在渐变起点之前将渐变扩展到所有点
0
不会以任何方式扩展渐变
为了在两端都扩展颜色,可以用逻辑 OR(使用|操作符)同时指定“之后”和“之前”参 数。我们看下面的例子:
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGPoint startPoint, endPoint;
startPoint = CGPointMake(0.0f, screenBounds.size.height / 2.0f);
endPoint = CGPointMake(screenBounds.size.width, startPoint.y);
CGContextDrawLinearGradient (currentContext, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
代码结束位置所释放的渐变句柄是我们在之前的示例代码中创建的。
这段代码的输出显然如图 9-1 所示。因为我们从最左边的点开始将渐变拖拽到最右边的点,所以我们不能利用 CGContextDrawLinearGradient
过程最后的“渐变绘制选项”参数。 让我们修改一下吧?绘制一个如图 9-2 所示的渐变如何?
我们会使用这部分早先用过的相同的代码来得到结果:
- (void)drawRect:(CGRect)rect
{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UIColor *startColor = [UIColor orangeColor];
CGFloat *startColorComponents = (CGFloat *)CGColorGetComponents([startColor CGColor]);
UIColor *endColor = [UIColor blueColor];
CGFloat *endColorComponents = (CGFloat *)CGColorGetComponents([endColor CGColor]);
CGFloat colorComponents[8] = {
/* Four components of the orange color (RGBA) */ startColorComponents[0],
startColorComponents[1],
startColorComponents[2],
startColorComponents[3], /* First color = orange */
/* Four components of the blue color (RGBA) */
endColorComponents[0],
endColorComponents[1],
endColorComponents[2],
endColorComponents[3], /* Second color = blue */
};
CGFloat colorIndices[2] = {
0.0f, /* Color 0 in the colorComponents array */
1.0f, /* Color 1 in the colorComponents array */
};
CGGradientRef gradient = CGGradientCreateWithColorComponents (colorSpace,
(const CGFloat *)&colorComponents,
(const CGFloat *)&colorIndices,2);
CGColorSpaceRelease(colorSpace);
CGPoint startPoint, endPoint;
startPoint = CGPointMake(120, 260);
endPoint = CGPointMake(200.0f, 220);
CGContextDrawLinearGradient (currentContext, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
CGContextRestoreGState(currentContext);
}
你可能难以理解将 kCGGradientDrawsBeforeStartLocation
和 kCGGradientDrawsAfterEndLocation
值混合传入 CGContextDrawLinearGradient
过程,是如何 产生如图 9-2 所示的对角线效果的。那么让我们将这些值从 CGContextDrawLinearGradient
过程的那个参数中去掉,仅仅像之前一样传入 0。图9-3 显示了结果:
很容易得出结论,图 9-2 和图 9-3 中使用的是相同的渐变。但是,图 9-2 中的渐 变扩展了起点和终点的颜色,使渐变充满整个图形环境,这就是为什么你能看见整个屏幕被颜色所覆盖。