2012-02-25 11 views
6

Sto creando un riflettore che si muove sul contenuto nella mia app, in questo modo:Creazione di un overlay trasparente animatable con strati Core Animation

Spotlight ASpotlight B

Nell'applicazione del campione (vedi sopra), lo sfondo il livello è blu e su di esso ho uno strato che si scurisce tutto tranne un cerchio che lo mostra normalmente. Ho funzionato (puoi vedere come nel codice qui sotto). Nella mia vera app, ci sono contenuti reali in altri CALayer, piuttosto che blu.

Ecco il mio problema: non si anima. Sto usando il disegno CGContext per creare il cerchio (che è un punto vuoto in un livello altrimenti nero). Quando fai clic sul pulsante nella mia app di esempio, disegno il cerchio con una dimensione diversa in una posizione diversa.

Mi piacerebbe che per tradurre e ridimensionare senza problemi, invece di saltare, come al momento. Potrebbe essere necessario un diverso metodo per creare l'effetto spotlight o potrebbe non esserci un modo per cui non conosco per animare implicitamente la chiamata -drawLayer:inContext:.

E 'facile creare l'applicazione di esempio:

  1. fare un nuovo cacao app (usando ARC)
  2. Aggiungere il quadro Quartz
  3. goccia una visualizzazione personalizzata e un pulsante sul XIB
  4. Collega la visualizzazione personalizzata a una nuova classe (SpotlightView), con codice fornito sotto
  5. Elimina SpotlightView.h, poiché ho incluso il suo contenuto in SpotlightView.m
  6. Impostare uscita del pulsante per l'azione -moveSpotlight:

Update (la proprietà mask)

Mi piace il suggerimento di David Rönnqvist nei commenti l'utilizzo della proprietà mask dello strato oscurato per tagliare un buco, che Potrei quindi muovermi indipendentemente. Il problema è che per qualche motivo, la proprietà mask funziona al contrario di come mi aspetto che una maschera funzioni. Quando specifico una maschera circolare, tutto ciò che appare è il cerchio. Mi aspettavo che la maschera funzionasse nel modo opposto, mascherando l'area con 0 alfa.

Il mascheramento sembra la soluzione giusta per questo, ma se devo riempire l'intero livello e ritagliare un buco, allora posso anche farlo nel modo in cui ho originariamente pubblicato. Qualcuno sa come invertire la proprietà -[CALayer mask], in modo che l'area disegnata venga ritagliata dall'immagine del livello?

/Aggiornamento

Ecco il codice per SpotlightView:

// 
// SpotlightView.m 
// 

#import <Quartz/Quartz.h> 

@interface SpotlightView : NSView 
- (IBAction)moveSpotlight:(id)sender; 
@end 

@interface SpotlightView() 
@property (strong) CALayer *spotlightLayer; 
@property (assign) CGRect highlightRect; 
@end 


@implementation SpotlightView 

@synthesize spotlightLayer; 
@synthesize highlightRect; 

- (id)initWithFrame:(NSRect)frame { 
    if ((self = [super initWithFrame:frame])) { 
     self.wantsLayer = YES; 

     self.highlightRect = CGRectNull; 

     self.spotlightLayer = [CALayer layer]; 
     self.spotlightLayer.frame = CGRectInset(self.layer.bounds, -50, -50); 
     self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; 

     self.spotlightLayer.opacity = 0.60; 
     self.spotlightLayer.delegate = self; 

     CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; 
     [blurFilter setValue:[NSNumber numberWithFloat:5.0] 
         forKey:@"inputRadius"]; 
     self.spotlightLayer.filters = [NSArray arrayWithObject:blurFilter]; 

     [self.layer addSublayer:self.spotlightLayer]; 
    } 

    return self; 
} 

- (void)drawRect:(NSRect)dirtyRect {} 

- (void)moveSpotlight:(id)sender { 
    [self.spotlightLayer setNeedsDisplay]; 
} 

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { 
    if (layer == self.spotlightLayer) { 
     CGContextSaveGState(ctx); 

     CGColorRef blackColor = CGColorCreateGenericGray(0.0, 1.0); 

     CGContextSetFillColorWithColor(ctx, blackColor); 
     CGColorRelease(blackColor); 

     CGContextClearRect(ctx, layer.bounds); 
     CGContextFillRect(ctx, layer.bounds); 

     // Causes the toggling 
     if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) { 
      self.highlightRect = CGRectMake(25, 25, 100, 100); 
     } else { 
      self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50, 
              NSMaxY(self.layer.bounds) - 50, 
              25, 25); 
     } 

     CGRect drawnRect = [layer convertRect:self.highlightRect 
            fromLayer:self.layer]; 
     CGMutablePathRef highlightPath = CGPathCreateMutable(); 
     CGPathAddEllipseInRect(highlightPath, NULL, drawnRect); 
     CGContextAddPath(ctx, highlightPath); 

     CGContextSetBlendMode(ctx, kCGBlendModeClear); 
     CGContextFillPath(ctx); 

     CGPathRelease(highlightPath); 
     CGContextRestoreGState(ctx); 
    } 
    else { 
     CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0); 
     CGContextSetFillColorWithColor(ctx, blueColor); 
     CGContextFillRect(ctx, layer.bounds); 
     CGColorRelease(blueColor); 
    } 
} 

@end 
+0

Avete provato a fissare una scala e tradurre trasformare sullo strato riflettori buio? –

+0

@ DavidRönnqvist Questo non funzionerà, perché il livello con il riflettore è la dimensione dell'intero 'superlayer' – Dov

+0

Il riflettore potrebbe essere la maschera al livello scuro? È possibile disegnare il riflettore nel proprio livello e impostarlo come -mask: proprietà sul livello scuro. In questo modo penso che potresti animare la dimensione e la posizione del riflettore indipendentemente dalle dimensioni e dalla posizione del livello scuro. Non l'hai ancora provato. –

risposta

6

ho finalmente capito. Ciò che mi ha spinto alla risposta è stato l'ascolto della classe CAShapeLayer. All'inizio pensavo che sarebbe stato un modo più semplice per disegnare il contenuto del livello, piuttosto che disegnare e cancellare il contenuto di uno standard CALayer.Ma ho letto la documentazione della proprietà path di CAShapeLayer, che affermava che poteva essere animata, ma non implicitamente.

Mentre una maschera di livello avrebbe potuto essere più intuitiva ed elegante, non sembra essere possibile utilizzare la maschera per nascondere una porzione dello strato del proprietario, piuttosto che mostrando una parte, e così ho non potrei usarlo Sono felice con questa soluzione, in quanto è abbastanza chiaro cosa sta succedendo. Vorrei che usasse l'animazione implicita, ma il codice di animazione è solo poche righe.

In basso, ho modificato il codice di esempio dalla domanda per aggiungere un'animazione fluida. (ho rimosso il codice CIFilter, perché era estranea. La soluzione non funzionano ancora con i filtri.)

// 
// SpotlightView.m 
// 

#import <Quartz/Quartz.h> 

@interface SpotlightView : NSView 
- (IBAction)moveSpotlight:(id)sender; 
@end 

@interface SpotlightView() 
@property (strong) CAShapeLayer *spotlightLayer; 
@property (assign) CGRect highlightRect; 
@end 


@implementation SpotlightView 

@synthesize spotlightLayer; 
@synthesize highlightRect; 

- (id)initWithFrame:(NSRect)frame { 
    if ((self = [super initWithFrame:frame])) { 
     self.wantsLayer = YES; 

     self.highlightRect = CGRectNull; 

     self.spotlightLayer = [CAShapeLayer layer]; 
     self.spotlightLayer.frame = self.layer.bounds; 
     self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; 
     self.spotlightLayer.fillRule = kCAFillRuleEvenOdd; 

     CGColorRef blackoutColor = CGColorCreateGenericGray(0.0, 0.60); 
     self.spotlightLayer.fillColor = blackoutColor; 
     CGColorRelease(blackoutColor); 

     [self.layer addSublayer:self.spotlightLayer]; 
    } 

    return self; 
} 

- (void)drawRect:(NSRect)dirtyRect {} 

- (CGPathRef)newSpotlightPathInRect:(CGRect)containerRect 
         withHighlight:(CGRect)spotlightRect { 
    CGMutablePathRef shape = CGPathCreateMutable(); 

    CGPathAddRect(shape, NULL, containerRect); 

    if (!CGRectIsNull(spotlightRect)) { 
     CGPathAddEllipseInRect(shape, NULL, spotlightRect); 
    } 

    return shape; 
} 

- (void)moveSpotlight { 
    CGPathRef toShape = [self newSpotlightPathInRect:self.spotlightLayer.bounds 
             withHighlight:self.highlightRect]; 

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
    pathAnimation.fromValue = (__bridge id)self.spotlightLayer.path; 
    pathAnimation.toValue = (__bridge id)toShape; 

    [self.spotlightLayer addAnimation:pathAnimation forKey:@"path"]; 
    self.spotlightLayer.path = toShape; 

    CGPathRelease(toShape); 
} 

- (void)moveSpotlight:(id)sender { 
    if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) { 
     self.highlightRect = CGRectMake(25, 25, 100, 100); 
    } else { 
     self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50, 
             NSMaxY(self.layer.bounds) - 50, 
             25, 25); 
    } 

    [self moveSpotlight]; 
} 

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { 
    CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0); 
    CGContextSetFillColorWithColor(ctx, blueColor); 
    CGContextFillRect(ctx, layer.bounds); 
    CGColorRelease(blueColor); 
} 

@end 
+0

Grazie mille, questo mi ha davvero aiutato! Avevo bisogno di qualcosa per far sì che il mio cerchio segmentato si restringesse, senza in realtà ridursi. Ora posso farlo. – Jelle

Problemi correlati