2010-07-01 6 views
14

EDIT:Dessiner un UIView arrondi avec gradient et ombre portée

J'ai finalement trouvé une vraie solution simple à ce problème, en utilisant la classe CAGradientLayer, et les fonctionnalités de dessin CALayer.
Ole Begemann a publié un excellent wrapper UIView pour la classe CAGradientLayer nommée OBGradientView.
Cette classe vous permet de créer facilement un gradient UIView dans votre application.
Vous utilisez ensuite les fonctionnalités de dessin CALayer ajouter les coins arrondis et déposez les valeurs d'ombre:

// 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]; 

Ne pas oublier d'ajouter le cadre QuartzCore à votre projet.



QUESTION ORIGINAL:

Je travaille sur un contrôle personnalisé qui est un bouton rectangle arrondi, rempli d'un gradient linéaire, et ayant une ombre portée. J'ai rempli les deux premières étapes en utilisant cette réponse: link text

Mon problème est maintenant d'ajouter une ombre portée sous la forme résultante. En fait, le contexte a été coupé au rectangle arrondi, donc quand j'utilise la fonction CGContextSetShadow, il ne le dessine pas.

J'ai essayé de résoudre ce problème en dessinant deux fois le rectangle arrondi, d'abord avec une couleur unie, afin de dessiner l'ombre, puis de la redessiner avec le dégradé.

Il a travaillé un peu, mais je peux encore voir quelques pixels dans les coins de la forme résultant du premier tirage au sort avec une couleur unie, comme vous pouvez le voir sur cette version zoomée:

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

il est presque bon, mais pas encore parfait ...

Voici mon -drawRect: la mise en œuvre:

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

Toutes les idées sur la façon de le faire d'une autre manière?

Merci!

Répondre

24

Afin de créer une vue d'angle arrondi avec un fond dégradé et laisser tomber l'ombre, voici ce qui a fait:

La première partie est très similaire à celle fournie dans la question, elle crée un chemin rectangle arrondi en utilisant CGPathAddArcToPoint comme décrit très bien dans this article.Voici une photo pour me aider à le comprendre: alt text

La deuxième partie fonctionne comme suit:

activer l'observation du contexte graphique, ajoutez le chemin qui vient d'être défini, puis remplissez ce chemin. Vous ne pouvez pas appliquer l'ombre uniquement au chemin lui-même (les chemins ne font pas partie de l'état graphique), vous devez donc remplir le chemin pour que l'ombre apparaisse (je suppose qu'un chemin strié peut également fonctionner?). Vous ne pouvez pas simplement appliquer l'ombre à un dégradé car ce n'est pas vraiment un remplissage standard (voir this post pour plus d'informations). Une fois que vous avez un rectangle arrondi rempli qui crée l'ombre, vous devez dessiner le dégradé au-dessus de celui-ci. Ajoutez donc le chemin une seconde fois afin de définir la zone de découpage, puis dessinez le dégradé à l'aide de CGContextDrawLinearGradient. Je ne pense pas que vous puissiez facilement "remplir" un chemin avec un dégradé comme vous le feriez avec l'étape de remplissage standard précédente, remplissez plutôt la zone de dessin avec le dégradé, puis reliez-la au rectangle arrondi qui vous intéresse

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

Merci beno pour cette explication très détaillée! Votre exemple fonctionne parfaitement. Je voudrais juste corriger votre déclaration CGGradientRef de CGGradientRef * normalGradient à CGGradientRef normalGradient. –

+0

Pouvez-vous expliquer comment les lignes tangentes et les points sont créés à partir des arguments? Je ne comprends pas comment vous pouvez créer deux lignes et deux points à partir de 4 arguments ... – ryyst

+0

@super_tomtom quelqu'un peut-il expliquer comment cela fonctionne? J'ai sous-classes un UIView personnalisé et fait cette fonction c'est drawRect override mais je n'ai toujours pas de coins arrondis ou ombre portée? –

1

Pour les ombres, vous pouvez utiliser CGContextSetShadow()

Ce code dessiner quelque chose avec une ombre:

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

Oui, cela fonctionne sûrement, mais mon problème est que je veux remplir la forme avec un dégradé. Pour ce faire, j'ai besoin d'utiliser la fonction CGContextClip. Et une fois que le contexte est coupé, il ne semble plus dessiner l'ombre. –

2

J'ai une solution qui ne nécessite pas de pré-remplissage du chemin. Avantage (?) Est que l'ombre peut utiliser des effets de transparence du gradient (c'est-à-dire si le gradient est d'opaque à transparent, l'ombre sera également partiellement transparente) et est plus simple.

Il va plus ou moins comme:

CGContextSetShadowWithColor(); 
CGContextBeginTransparencyLayer(); 

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

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

Je suppose que est beacuse CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer est en dehors de la pince et l'ombre est appliquée sur cette couche (qui contient chemin rempli de gradient). Au moins, ça a l'air de marcher pour moi.

1

Votre problème (d'origine) était que vous dessiniez à nouveau une ombre lorsque vous dessiniez le dégradé. Cette ombre avait un décalage (0,0) et un peu de flou, qui ne brille que sur les coins. Dans la ligne avant CGContextDrawLinearGradient (...), ajoutez ce qui suit:

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

La valeur de couleur NULL désactive et shadowing supprime l'effet de coin.

Questions connexes