简体   繁体   English

绘制带有渐变和阴影的圆形UIView

[英]Draw a rounded UIView with gradient and drop shadow

EDIT: 编辑:

I finally found a real simple solution to this problem, using the CAGradientLayer class, and the CALayer drawing functionalities. 我终于找到了一个真正简单的解决方案,使用CAGradientLayer类和CALayer绘图功能。
Ole Begemann released a great UIView wrapper for CAGradientLayer class named OBGradientView . Ole Begemann为名为OBGradientView的 CAGradientLayer类发布了一个很棒的UIView包装器。
This class allows you to easily create a gradient UIView in your application. 该类允许您在应用程序中轻松创建渐变UIView。
You then use the CALayer drawing functionalities to add the rounded corners and drop shadow values : 然后使用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];

Dont forget to add the QuartzCore framework to your project. 别忘了将QuartzCore框架添加到您的项目中。



ORIGINAL QUESTION: 原始问题:

I have been working on a custom control that is a rounded rectangle button, filled with a linear gradient, and having a drop shadow. 我一直在研究一个自定义控件,它是一个圆角矩形按钮,填充线性渐变,并有一个投影。 I have filled the two first steps using this answer : link text 我使用这个答案填写了前两个步骤: 链接文本

My problem is now to add a drop shadow under the resulting shape. 我现在的问题是在生成的形状下添加一个阴影。 Actually, the context has been clipped to the rounded rect path, so when I use the CGContextSetShadow function, it doesn't draw it. 实际上,上下文被剪切到圆角矩形路径,所以当我使用CGContextSetShadow函数时,它不会绘制它。

I tried to solve this problem by drawing the rounded rect twice, first with a plain color, so it draws the shadow, and then redraw it with the gradient fill. 我尝试通过绘制圆角矩形两次来解决这个问题,首先使用普通颜色,因此它绘制阴影,然后使用渐变填充重绘它。

It kinda worked, but I still can see a few pixels at the corners of the shape resulting from the first draw with a plain color, as you can see on this zoomed version : 它有点工作,但我仍然可以在第一次使用普通颜色绘制时看到形状角落处的几个像素,正如您在此缩放版本上看到的那样:

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

It is almost good, but not perfect yet... 它几乎是好的,但还不完美......

Here is my -drawRect: implementation : 这是我的-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);
}

Any ideas on how to do this in another way ? 关于如何以另一种方式做到这一点的任何想法?

Thanks ! 谢谢 !

In order to create a rounded corner view with a gradient background and drop shadow, here's what did: 为了创建一个带有渐变背景和投影的圆角视图,这是做了什么:

The first part is very similar to what was provided in the question, it creates a rounded rect path using CGPathAddArcToPoint as described very well in this article . 第一部分与问题中提供的内容非常相似,它使用CGPathAddArcToPoint创建一个圆角矩形路径,如本文所述 Here's a picture to help me understand it: 这是一张图片,可以帮助我理解它: 替代文字

The second part works as follows: 第二部分的工作原理如下:

Enable shadowing on the graphics context, add the path that was just defined, then fill that path. 在图形上下文中启用阴影,添加刚刚定义的路径,然后填充该路径。 You can't apply the shadow to just the path itself (paths are not part of the graphics state), so you need to fill the path in order for the shadow to appear (I suppose a stroked path might also work?). 您不能仅将阴影应用于路径本身(路径不是图形状态的一部分),因此您需要填充路径以便显示阴影(我猜一条描边路径也可能起作用?)。 You can't simply apply the shadow to a gradient since it's not really a standard fill (see this post for more info). 您不能简单地将阴影应用于渐变,因为它不是真正的标准填充(有关详细信息,请参阅此文章 )。

Once you have a filled rounded rect that creates the shadow, you need to draw the gradient over top of that. 一旦你有一个填充圆角矩形创建阴影,你需要在其上方绘制渐变。 So add the path a second time in order to set the clipping area, then draw the gradient using CGContextDrawLinearGradient. 因此,第二次添加路径以设置剪切区域,然后使用CGContextDrawLinearGradient绘制渐变。 I don't think you can easily "fill" a path with a gradient like you could with the earlier standard-fill step, so instead you fill the drawing area with the gradient and then clip to the rounded rectangle area that you're interested in. 我认为您不能像使用早期标准填充步骤那样使用渐变轻松“填充”路径,因此请使用渐变填充绘图区域,然后剪切到您感兴趣的圆角矩形区域在。

- (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;
}

I have solution that does not need pre-fill of the path. 我有解决方案,不需要预填充路径。 Advantage(?) is that the shadow can use transparency effects of the gradient (ie if gradient is from opaque to trasparent, shadow will be partially transparent as well) and is simpler. 优点(?)是阴影可以使用渐变的透明效果(即,如果渐变从不透明到透明,阴影也将部分透明)并且更简单。

It goes more or less like: 它或多或少像:

CGContextSetShadowWithColor();
CGContextBeginTransparencyLayer();

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

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

I suppose that is beacuse CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer is outside the clip and the shadow is applied to that layer (which contains gradient filled path). 我想这是因为CGContextBeginTransparencyLayer / CGContextEndTransparencyLayer在剪辑之外并且阴影应用于该层(其包含渐变填充路径)。 At least it seems to work for me. 至少它似乎对我有用。

For shadows you can use CGContextSetShadow() 对于阴影,您可以使用CGContextSetShadow()

This code will draw something with a shadow: 这段代码会用阴影绘制一些东西:

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

Your (original) problem was that you were again drawing a shadow when you drew the gradient. 您(原始)的问题是,当您绘制渐变时,您再次绘制阴影。 This shadow had a (0,0) offset and a little bit of blur, that only shines through on the corners. 这个阴影有一个(0,0)偏移和一点点模糊,只有角落才会闪耀。 In the line before CGContextDrawLinearGradient(…), add the following: 在CGContextDrawLinearGradient(...)之前的行中,添加以下内容:

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

The NULL color value disables shadowing and will remove the corner effect. NULL颜色值禁用阴影并将删除角落效果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM