2010-07-01 15 views
14

EDIT fallen:eine abgerundete UIView mit Steigung Zeichnen und Schatten

finde ich endlich eine echte einfache Lösung für dieses Problem, die CAGradientLayer-Klasse und die CALayer Zeichnung Funktionalitäten.
Ole Begemann veröffentlichte einen großen UIView-Wrapper für die CAGradientLayer-Klasse OBGradientView.
Mit dieser Klasse können Sie auf einfache Weise in Ihrer Anwendung eine UIView mit Verlauf erstellen.
Sie dann verwenden, um die CALayer Zeichnung Funktionalitäten die abgerundeten Ecken hinzuzufügen und Schattenwerte fallen:

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

Vergessen Sie nicht, den Rahmen zu Quartz Projekt hinzuzufügen.



ursprüngliche Frage:

Ich habe auf einem kundenspezifischen Steuerung arbeitet, die ein abgerundetes Rechteck Knopf ist, mit einem linearen Gradienten gefüllt ist, und einen Schlagschatten aufweisen. Ich habe die ersten beiden Schritte mit dieser Antwort ausgefüllt: link text

Mein Problem ist jetzt, einen Schlagschatten unter der resultierenden Form hinzuzufügen. Eigentlich wurde der Kontext auf den abgerundeten rect-Pfad abgeschnitten. Wenn ich also die CGContextSetShadow-Funktion verwende, wird sie nicht gezeichnet.

Ich habe versucht, dieses Problem zu lösen, indem ich das gerundete Rechteck zweimal zuerst mit einer einfachen Farbe zeichne, so dass es den Schatten zeichnet und dann mit der Gradientenfüllung neu zeichnet.

Es funktionierte irgendwie, aber ich kann noch ein paar Pixel an den Ecken der Form aus der ersten Ziehung mit einer einfachen Farbe sehen, wie Sie auf dieser gezoomte Version sehen:

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

es ist fast gut, aber noch nicht perfekt ...

Hier ist meine -drawRect: Umsetzung:

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

Irgendwelche Ideen auf, wie man Tun Sie das auf andere Weise?

Danke!

Antwort

24

Um eine abgerundete Ecke Ansicht mit einem Gradienten Hintergrund zu schaffen und Schatten, hier ist was tat:

Der erste Teil ist sehr ähnlich zu dem, was in der Frage zur Verfügung gestellt wurde, es erstellt einen abgerundeten rekt Pfad mit CGPathAddArcToPoint wie sehr gut in this article beschrieben.Hier ist ein Bild mir, es zu helfen, zu verstehen: alt text

Der zweite Teil funktioniert wie folgt:

aktiviert auf dem Grafikkontext Shadowing, fügen Sie den Pfad, der gerade definiert wurde, dann diesen Pfad füllen. Sie können den Schatten nicht nur auf den Pfad selbst anwenden (Pfade sind nicht Teil des Grafikstatus), Sie müssen also den Pfad ausfüllen, damit der Schatten angezeigt wird (ich nehme an, ein strichliger Pfad funktioniert möglicherweise auch?). Sie können den Schatten nicht einfach auf einen Farbverlauf anwenden, da es sich nicht wirklich um eine Standardfüllung handelt (siehe this post für weitere Informationen).

Sobald Sie ein gefülltes gerundetes Rechteck haben, das den Schatten erzeugt, müssen Sie den Verlauf darüber zeichnen. Fügen Sie den Pfad ein zweites Mal hinzu, um den Ausschneidebereich festzulegen, und zeichnen Sie dann den Farbverlauf mit CGContextDrawLinearGradient. Ich glaube nicht, dass Sie einen Pfad mit einem Farbverlauf leicht füllen können, wie Sie es mit dem früheren Standardfüllschritt tun könnten. Stattdessen füllen Sie den Zeichenbereich mit dem Farbverlauf und clipsen dann an dem abgerundeten rechteckigen Bereich, an dem Sie interessiert sind 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

Vielen Dank für diese sehr detaillierte Erklärung! Ihr Beispiel funktioniert perfekt. Ich würde nur Ihre CGGradientRef-Deklaration von CGGradientRef * normalGradient zu CGGradientRef normalGradient korrigieren. –

+0

Können Sie erklären, wie die Tangenten und Punkte aus den Argumenten erzeugt werden? Ich verstehe nicht, wie Sie zwei Zeilen und zwei Punkte aus 4 Argumente erstellen können ... – ryyst

+0

@super_tomtom kann jemand erklären, wie das funktioniert? Ich habe eine benutzerdefinierte UIView-Klasse unterklassifiziert und diese Funktion zu ihrer drawRect-Überschreibung gemacht, aber ich bekomme immer noch keine abgerundeten Ecken oder Schlagschatten? –

1

Für Schatten können Sie CGContextSetShadow()

Dieser Code verwenden wird etwas mit einem Schatten zeichnen:

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

Ja, das funktioniert sicherlich, aber mein Problem ist, dass ich die Form mit einem Gradienten füllen möchte. Um dies zu tun, muss ich die CGContextClip-Funktion verwenden. Und sobald der Kontext abgeschnitten ist, scheint er den Schatten nicht mehr zu zeichnen. –

2

Ich habe eine Lösung, die nicht Pre-Fill des Pfades benötigt. Vorteil (?) Ist, dass der Schatten Transparenzeffekte des Gradienten verwenden kann (d. H. Wenn der Gradient von undurchsichtig zu transparent ist, wird der Schatten auch teilweise transparent sein) und ist einfacher.

Es geht mehr oder weniger wie:

CGContextSetShadowWithColor(); 
CGContextBeginTransparencyLayer(); 

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

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

Ich nehme an, ist beacuse CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer außerhalb der Clip und der Schatten auf diese Schicht aufgebracht wird (was Gradienten gefüllt Pfad enthält). Zumindest scheint es für mich zu funktionieren.

1

Ihr (ursprüngliches) Problem war, dass Sie beim Zeichnen des Farbverlaufs wieder einen Schatten gezeichnet haben. Dieser Schatten hatte einen (0,0) Offset und ein bisschen Unschärfe, die nur an den Ecken durchscheinen. In der Zeile vor CGContextDrawLinearGradient (...), fügen Sie folgendes:

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

Der NULL-Farbwert deaktiviert Abschattung und die Ecke Effekt entfernen.

Verwandte Themen