2010-07-01 153 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顏色值禁用陰影,並會刪除拐角效應。