2010-07-01 163 views
14

编辑:绘制一个圆角的UIView梯度和阴影

我终于找到了一个真正简单的解决这个问题,使用CAGradientLayer类和CALayer的绘图功能。
Ole Begemann发布了一个名为OBGradientView的CAGradientLayer类的优秀UIView包装器。
该类允许您在应用程序中轻松创建渐变UIView。
然后,您使用CALayer绘图功能添加圆角和阴影值:

// Create the gradient view 
OBGradientView *gradient = [[OBGradientView alloc] initWithFrame:someRect]; 
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor yellowColor], nil]; 
gradient.colors = colors; 

// Set rounded corners and drop shadow 
gradient.layer.cornerRadius = 5.0; 
gradient.layer.shadowColor = [UIColor grayColor].CGColor; 
gradient.layer.shadowOpacity = 1.0; 
gradient.layer.shadowOffset = CGSizeMake(2.0, 2.0); 
gradient.layer.shadowRadius = 3.0; 

[self.view addSubview:gradient]; 
[gradient release]; 

不要忘记将QuartzCore框架添加到项目中。



原来的问题:

我已经工作的自定义的控制是圆角矩形按钮,填充有线性梯度,并且具有阴影。 我已经用这个答案填充了两个第一步:link text

我的问题是现在在产生的形状下添加阴影。 实际上,上下文已被剪裁到四舍五入的矩形路径,所以当我使用CGContextSetShadow函数时,它不会绘制它。

我试图通过绘制圆形矩形两次来解决这个问题,首先用一个普通的颜色,所以它绘制阴影,然后用渐变填充重绘它。

它有点儿工作,但我仍然可以从第一次抽签产生了素色形状的角落看到几个像素,因为你可以在此放大版本,请参阅:

http://img269.imageshack.us/img269/6489/capturedcran20100701192.png

这几乎是好的,但不是很完美......

这里是我的-drawRect:实现:

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight) 
{ 
float fw, fh; 

if (ovalWidth == 0 || ovalHeight == 0) { 
    CGContextAddRect(context, rect); 
    return; 
} 
CGContextSaveGState(context); 
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect)); 
CGContextScaleCTM (context, ovalWidth, ovalHeight); 
fw = CGRectGetWidth (rect)/ovalWidth; 
fh = CGRectGetHeight (rect)/ovalHeight; 
CGContextMoveToPoint(context, fw, fh/2); 
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); 
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); 
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); 
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); 
CGContextClosePath(context); 
CGContextRestoreGState(context); 
} 


- (void)drawRect:(CGRect)rect 
{ 
CGContextRef context = UIGraphicsGetCurrentContext(); 

CGSize shadowOffset = CGSizeMake(10.0, 10.0); 
CGFloat blur = 5.0; 

rect.size.width -= shadowOffset.width + blur; 
rect.size.height -= shadowOffset.height + blur; 

CGContextSaveGState(context); 
addRoundedRectToPath(context, rect, _radius, _radius); 
CGContextSetShadow (context, shadowOffset, blur); 
CGContextDrawPath(context, kCGPathFill); 
CGContextRestoreGState(context); 

addRoundedRectToPath(context, rect, _radius, _radius); 
    CGContextClip(context); 

CGFloat colors[] = 
{ 
    _gradientStartColor.red, _gradientStartColor.green, _gradientStartColor.blue, _gradientStartColor.alpha, 
    _gradientEndColor.red, _gradientEndColor.green, _gradientEndColor.blue, _gradientEndColor.alpha 
}; 
size_t num_locations = 2; 
    CGFloat locations[2] = { 0.0, 1.0 }; 

CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); 
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgb, colors, locations, num_locations); 

CGRect currentBounds = self.bounds; 
CGPoint gStartPoint = CGPointMake(CGRectGetMidX(currentBounds), 0.0f); 
CGPoint gEndPoint = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds)); 
CGContextDrawLinearGradient(context, gradient, gStartPoint, gEndPoint, 0); 

CGColorSpaceRelease(rgb); 
CGGradientRelease(gradient); 
} 

如何任何想法以另一种方式做到这一点?

谢谢!

回答

24

为了创建一个渐变背景圆角视图和阴影,这里就是做:

第一部分与问题提供的内容非常相似,它使用CGPathAddArcToPoint创建了一个舍入矩形路径,如this article中所述。这里有一个图片来帮助我理解它: alt text

第二部分的工作原理如下:

启用阴影的图形上下文,加上刚刚定义的路径,然后填写该路径。您不能将阴影应用到路径本身(路径不是图形状态的一部分),所以您需要填充路径才能显示阴影(我认为描边路径也可能工作?)。您不能简单地将阴影应用于渐变,因为它不是标准填充(有关更多信息,请参阅this post)。

一旦你有一个填充的圆角矩形创建阴影,你需要绘制渐变的顶部。因此,第二次添加路径以设置裁剪区域,然后使用CGContextDrawLinearGradient绘制渐变。我不认为你可以像使用早期的标准填充步骤一样使用渐变来轻松“填充”渐变路径,所以您可以使用渐变填充绘制区域,然后剪裁到您感兴趣的圆角矩形区域英寸

- (void)drawRect:(CGRect)rect 
{ 
    [super drawRect:rect]; 

    CGGradientRef gradient = [self normalGradient]; 

    CGContextRef ctx = UIGraphicsGetCurrentContext(); 
    CGMutablePathRef outlinePath = CGPathCreateMutable(); 
    float offset = 5.0; 
    float w = [self bounds].size.width; 
    float h = [self bounds].size.height; 
    CGPathMoveToPoint(outlinePath, nil, offset*2.0, offset); 
    CGPathAddArcToPoint(outlinePath, nil, offset, offset, offset, offset*2, offset); 
    CGPathAddLineToPoint(outlinePath, nil, offset, h - offset*2.0); 
    CGPathAddArcToPoint(outlinePath, nil, offset, h - offset, offset *2.0, h-offset, offset); 
    CGPathAddLineToPoint(outlinePath, nil, w - offset *2.0, h - offset); 
    CGPathAddArcToPoint(outlinePath, nil, w - offset, h - offset, w - offset, h - offset * 2.0, offset); 
    CGPathAddLineToPoint(outlinePath, nil, w - offset, offset*2.0); 
    CGPathAddArcToPoint(outlinePath, nil, w - offset , offset, w - offset*2.0, offset, offset); 
    CGPathCloseSubpath(outlinePath); 

    CGContextSetShadow(ctx, CGSizeMake(4,4), 3); 
    CGContextAddPath(ctx, outlinePath); 
    CGContextFillPath(ctx); 

    CGContextAddPath(ctx, outlinePath); 
    CGContextClip(ctx); 
    CGPoint start = CGPointMake(rect.origin.x, rect.origin.y); 
    CGPoint end = CGPointMake(rect.origin.x, rect.size.height); 
    CGContextDrawLinearGradient(ctx, gradient, start, end, 0); 

    CGPathRelease(outlinePath); 
} 

- (CGGradientRef)normalGradient 
{ 

    NSMutableArray *normalGradientLocations = [NSMutableArray arrayWithObjects: 
               [NSNumber numberWithFloat:0.0f], 
               [NSNumber numberWithFloat:1.0f], 
               nil]; 


    NSMutableArray *colors = [NSMutableArray arrayWithCapacity:2]; 

    UIColor *color = [UIColor colorWithRed:0.2745 green:0.2745 blue:0.2745 alpha:1.0]; 
    [colors addObject:(id)[color CGColor]]; 
    color = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0]; 
    [colors addObject:(id)[color CGColor]]; 
    NSMutableArray *normalGradientColors = colors; 

    int locCount = [normalGradientLocations count]; 
    CGFloat locations[locCount]; 
    for (int i = 0; i < [normalGradientLocations count]; i++) 
    { 
     NSNumber *location = [normalGradientLocations objectAtIndex:i]; 
     locations[i] = [location floatValue]; 
    } 
    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); 

    CGGradientRef normalGradient = CGGradientCreateWithColors(space, (CFArrayRef)normalGradientColors, locations); 
    CGColorSpaceRelease(space); 

    return normalGradient; 
} 
+0

谢谢你beno这个非常详细的解释!你的例子完美地工作。我只是将您的CGGradientRef声明从CGGradientRef * normalGradient更正为CGGradientRef normalGradient。 – 2010-11-23 15:07:50

+0

你能解释从参数创建切线和点的方式吗?我不明白如何从4个参数中创建两行和两个点... – ryyst 2011-04-16 12:54:10

+0

@super_tomtom有人可以解释这是如何工作的吗?我已经子类化一个自定义的UIView,并使这个函数它的drawRect覆盖,但我仍然没有圆角或投影? – 2011-04-19 20:31:17

1

对于阴影您可以使用CGContextSetShadow()

此代码将绘制的东西有阴影:

- (void)drawTheRealThingInContext:(CGContextRef)ctx 
{ 
     // calculate x, y, w, h and inset here... 

    CGContextMoveToPoint(ctx, x+inset, y); 
    CGContextAddLineToPoint(ctx, x+w-inset, y); 
    CGContextAddArcToPoint(ctx, x+w, y, x+w, y+inset, inset); 
    CGContextAddLineToPoint(ctx, x+w, y+w-inset); 
    CGContextAddArcToPoint(ctx,x+w, y+w, x+w-inset, y+w, inset); 
    CGContextAddLineToPoint(ctx, x+inset, y+w); 
    CGContextAddArcToPoint(ctx,x, y+w, x, y+w-inset, inset); 
    CGContextAddLineToPoint(ctx, x, y+inset); 
    CGContextAddArcToPoint(ctx,x, y, x+inset, y, inset);  
} 
- (void)drawRect:(CGRect)rect { 

    CGContextRef ctx = UIGraphicsGetCurrentContext(); 
    CGFloat color[4];color[0] = 1.0;color[1] = 1.0;color[2] = 1.0;color[3] = 1.0; 
    CGFloat scolor[4];scolor[0] = 0.4;scolor[1] = 0.4;scolor[2] = 0.4;scolor[3] = 0.8; 

    CGContextSetFillColor(ctx, color); 

    CGContextSaveGState(ctx); 
    CGSize myShadowOffset = CGSizeMake (3, -3); 
    CGContextSetShadow (ctx, myShadowOffset, 1); 

    CGContextBeginPath(ctx); 

    [self drawTheRealThingInContext:ctx]; 

    CGContextFillPath(ctx); 
    CGContextRestoreGState(ctx); 
} 
+0

是的,这确实有效,但我的问题是我想用渐变填充形状。 为此,我需要使用CGContextClip函数。一旦上下文被剪切掉,它似乎不再画出阴影。 – 2010-07-02 07:46:10

2

我有解决方案,不需要预填充的路径。优点(?)是阴影可以使用渐变的透明效果(即,如果渐变从不透明到透明,阴影也会部分透明),并且更简单。

它去或多或少像:

CGContextSetShadowWithColor(); 
CGContextBeginTransparencyLayer(); 

CGContextSaveGState(); 
CGContextClip(); 
CGGradientCreateWithColorComponents(); 
CGContextRestoreGState(); 

CGContextEndTransparencyLayer(); 
CGContextSetShadowWithColor(..., NULL); 

我假定是怎么一回事,因为CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer是剪辑外面和阴影施加到该层(其包含梯度填充路径)。至少它似乎为我工作。

1

您的(原始)问题是您在绘制渐变时再次绘制阴影。这个影子有一个(0,0)的偏移量和一点点的模糊,只在角落闪耀。在CGContextDrawLinearGradient前行(...),添加以下内容:

CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 0, NULL); 

的NULL颜色值禁用阴影,并会删除拐角效应。