2010-07-01 15 views
14

EDIT:Disegnare un UIView arrotondato con gradiente e ombra

ho finalmente trovato un vero semplice soluzione a questo problema, utilizzando la classe CAGradientLayer, e le funzionalità di disegno CALayer.
Ole Begemann ha rilasciato un grande wrapper UIView per la classe CAGradientLayer denominata OBGradientView.
Questa classe consente di creare facilmente un UIView a gradiente nell'applicazione.
È quindi utilizzare i CALayer funzionalità di disegno per aggiungere gli angoli arrotondati e rilasciare i valori d'ombra:

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

Non dimenticate di aggiungere il quadro QuartzCore al progetto.



DOMANDA ORIGINALE:

Ho lavorato su un controllo personalizzato che un pulsante arrotondato rettangolo, riempito con un gradiente lineare, e avendo un'ombra. Ho completato i due primi passi utilizzando questa risposta: link text

Il mio problema è ora di aggiungere un'ombra esterna sotto la forma risultante. In realtà, il contesto è stato ritagliato nel percorso rect arrotondato, quindi quando uso la funzione CGContextSetShadow, non lo disegna.

Ho provato a risolvere questo problema disegnando il rettangolo arrotondato due volte, prima con un colore semplice, in modo da disegnare l'ombra e quindi ridisegnarlo con il riempimento sfumato.

kinda funzionato, ma posso ancora vedere qualche pixel agli angoli della forma risultante dal primo pareggio con un colore uniforme, come si può vedere in questa versione ingrandita:

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

E 'quasi buono, ma non ancora perfetto ...

Ecco la mia -drawRect: implementazione:

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

Tutte le idee su come fai questo in un altro modo?

Grazie!

risposta

24

Al fine di creare una vista angolo arrotondato con uno sfondo sfumato e ombra, ecco cosa ha fatto:

La prima parte è molto simile a quanto previsto nella domanda, crea un percorso rettale arrotondato utilizzando CGPathAddArcToPoint come descritto molto bene in this article.Ecco una foto di aiutarmi a capire è: alt text

La seconda parte funziona come segue:

Abilita shadowing sul contesto grafico, aggiungere il percorso che è stato appena definito, quindi riempire quel percorso. Non è possibile applicare l'ombra solo al percorso stesso (i percorsi non fanno parte dello stato della grafica), quindi è necessario riempire il percorso in modo che l'ombra appaia (suppongo che anche un percorso tracciato possa funzionare?). Non puoi semplicemente applicare l'ombra a un gradiente poiché non è realmente un riempimento standard (vedi this post per maggiori informazioni).

Una volta che un rettangolo arrotondato pieno crea l'ombra, è necessario disegnare la sfumatura sopra di essa. Quindi aggiungi il percorso una seconda volta per impostare l'area di ritaglio, quindi disegna il gradiente usando CGContextDrawLinearGradient. Non penso che puoi facilmente "riempire" un tracciato con una sfumatura come potresti con la precedente fase di riempimento standard, quindi invece riempi l'area di disegno con la sfumatura e poi agganci all'area del rettangolo arrotondato a cui sei interessato 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; 
} 
+0

Grazie mille per questa spiegazione molto dettagliata! Il tuo esempio funziona perfettamente. Correggerei semplicemente la dichiarazione CGGradientRef da CGGradientRef * normalGradient a CGGradientRef normalGradient. –

+0

Puoi spiegare come vengono create le linee e i punti tangenti dagli argomenti? Non capisco come sia possibile creare due righe e due punti da 4 argomenti ... – ryyst

+0

@super_tomtom qualcuno può spiegare come funziona? Ho sottoclassi un UIView personalizzato e ho fatto questa funzione è drawRect override ma non ho ancora angoli arrotondati o drop shadow? –

1

Per ombre è possibile utilizzare CGContextSetShadow()

Questo codice disegnare qualcosa con un'ombra:

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

Sì, questo funziona sicuramente, ma il mio problema è che voglio riempire la forma con un gradiente. Per fare ciò, ho bisogno di usare la funzione CGContextClip. E una volta che il contesto è troncato, non sembra più disegnare l'ombra. –

2

Ho una soluzione che non ha bisogno di pre-riempimento del percorso. Vantaggio (?) È che l'ombra può usare gli effetti di trasparenza del gradiente (cioè se il gradiente è da opaco a trasparente, anche l'ombra sarà parzialmente trasparente) ed è più semplice.

Va più o meno come:

CGContextSetShadowWithColor(); 
CGContextBeginTransparencyLayer(); 

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

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

immagino che è Allucinante CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer è fuori la clip e l'ombra viene applicato a tale strato (che contiene gradiente percorso riempito). Almeno sembra funzionare per me.

1

Il tuo problema (originale) era che stavi disegnando un'ombra quando hai disegnato il gradiente. Questa ombra ha uno scostamento (0,0) e un po 'sfocato, che traspare solo negli angoli. Nella linea prima CGContextDrawLinearGradient (...), aggiungere il seguente: valore del colore

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

Il NULL disabilita shadowing e rimuoverà l'effetto angolo.

Problemi correlati