IOS动画杂七杂八篇iOS Developer

图形与动画(三)--附Demo

2016-07-13  本文已影响286人  0o冻僵的企鹅o0

最近在看《iOS 6 Programming Cookbook》的翻译版,它由DevDiv论坛的网友翻译,原文地址:点击跳转。由于下载的pdf都有水印,并且排版不是很好,特别是代码排版,基本不能看,所以这里就整理了一下,方便再次查看。另外把里面提到的点写了一个demo,由于里面一些代码现在已经废弃,所以demo中都是用的新api,下载地址在这里:图形与动画Demo

1.8 为形状增加阴影

使用 CGContextSetShadow 。用core graphics绘制阴影很容易。图形环境是容纳阴影的元素。就是说,你要先对环境应用阴影,再绘制需要阴影的形状,最后要将阴影从环境上移除(或者设置一个新环境)。

core graphics 中,我们可以使用下列两个过程来为图形环境应用阴影:

CGContextSetShadow 过程 

这个过程会创建黑色或灰色的阴影,它接受三个参数:

  1. 要使用阴影的图形环境;
  2. 阴影的位移,由 CGSize 类型值指定,从每个形状要应用阴影的右下部分开始。位移 的 x 值越大,形状右边的阴影就扩散得越远。位移的 y 值越大,下部的阴影就越低;
  3. 阴影的模糊值,以浮点值(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 中没有要求一个阴影, 但是如果你跑一下程序,你会看到类似下图 所示的结果

图8-2 应用到第二个矩形的阴影不是我们想要的.png

你马上观察到屏幕下方的第二个矩形被应用了阴影效果。为了避免这个,我们将在对图
形环境应用阴影效果之前保存它的状态,并在我们想去掉阴影效果时,恢复之前的状态。

广而言之,图形环境状态的存取不仅限于阴影。恢复图形环境的状态会恢复一切(填充
色,字体,线宽,等等)到你之前的设置。所以如果你同时也设置了填充和画笔颜色,这些
颜色也会被重置。

你可以通过 CGContextSaveGState 过程来保存图形环境的状态,而通过 CGContextRestoreGState 过程恢复之前的状态。所以如果修改 drawRectAtTopOfScreen 过 程,在应用阴影前保存图形环境状态,并在绘制路径后恢复其状态,我们将得到不同的结 果,如下图 所示:

图8-3 保存图形环境状态来准确的使用阴影

修改后的代码:

- (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)

图9-1 一个开始于蓝色结束于绿色的轴向渐变

这里只讨论轴向渐变。轴向渐变是以一种颜色为起点,而以另一种颜色为终点的渐变(虽然可以在起点和终点用一种颜色,但是那不会产生渐变)。“轴向”的意思是和某个轴有关。 两个点(起点和终点)形成一条线段,这就是渐变要被绘制的轴。

为了创建一个轴向渐变,你必须调用 CGGradientCreateWithColorComponents 函数。函数的返回值是一个 CGGradientRef 类型的渐变。它是渐变的句柄。当你用完渐变后,你必须调用 CGGradientRelease 释放掉。

CGGradientCreateWithColorComponents 函数接受四个参数:

我们来看例子。假设我们想绘制如图 图9-1 所示的渐变,下面是步骤:

  1. 选择渐变的开始和结束点 -- 它变换的轴。这里,我们选择了从左向右移动。想像你在沿着一条假想的水平线移动时改变颜色。沿着那条线,我们会传播这些颜色使得每条垂直于这条水平线的线上只包含一种颜色。这里,这些垂直的线将会是图 9-1 中的垂线。近距离观察那些垂直线,其中的每一条从上到下都只包含一种颜色。这就是轴向渐变的原理。
  2. 如前所述,现在我们要创建一个颜色空间来作为 CGGradientCreateWithColorComponents 函数的第一个参数:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

在我们用完这个颜色空间后,我们会释放它。

  1. 根据图 9-1 所选择的颜色,我们选择蓝色作为起点(左边),绿色作为终点(右边)。我选择的变量名(startColorComponentsendColorComponents)是特意选定来帮助我们记住我们对每个颜色在做的事情。实际上我们会使用数组位置来指定起点和终点:
UIColor *startColor = [UIColor blueColor];
CGFloat *startColorComponents =
(CGFloat *)CGColorGetComponents([startColor CGColor]); UIColor *endColor = [UIColor greenColor];
CGFloat *endColorComponents =
(CGFloat *)CGColorGetComponents([endColor CGColor]);
  1. 在你获得每个颜色的组件后,我们将他们放到一个简单的数组中,以传入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 */
 };
  1. 因为这个数组只有两种颜色,我们需要指定第一个是渐变的最开始(位置 0.0)而第二个在最后(位置 1.0)。让我们将这些索引放到数组中作为参数传递到 CGGradientCreateWithColorComponents 函数:
CGFloat colorIndices[2] = {
0.0f, /* Color 0 in the colorComponents array */ 
1.0f, /* Color 1 in the colorComponents array */
};
  1. 现在我们要做的只剩下用之前创建的参数实际调用 CGGradientCreateWithColorComponents 函数:
CGGradientRef gradient = CGGradientCreateWithColorComponents 
(colorSpace,
(const CGFloat *)&colorComponents,
(const CGFloat *)&colorIndices,
2);
  1. 非常棒!现在我们在 gradient 变量中保存了我们的渐变对象。在我们忘记之前,还必须释放之前在 CGColorSpaceCreateDeviceRGB 函数中创建的颜色空间:
CGColorSpaceRelease(colorSpace);

现在我们将使用 CGContextDrawLinearGradient 来往图形环境上面绘制轴向渐变。这个 过程接受 5 个参数:

 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 所示的渐变如何?

图 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);
}

你可能难以理解将 kCGGradientDrawsBeforeStartLocationkCGGradientDrawsAfterEndLocation 值混合传入 CGContextDrawLinearGradient 过程,是如何 产生如图 9-2 所示的对角线效果的。那么让我们将这些值从 CGContextDrawLinearGradient 过程的那个参数中去掉,仅仅像之前一样传入 0。图9-3 显示了结果:

图 9-3 不使用拉伸颜色(stretched colors)的轴向渐变

很容易得出结论,图 9-2 和图 9-3 中使用的是相同的渐变。但是,图 9-2 中的渐 变扩展了起点和终点的颜色,使渐变充满整个图形环境,这就是为什么你能看见整个屏幕被颜色所覆盖。

上一篇下一篇

猜你喜欢

热点阅读