2012-02-25 17 views
6

Estoy creando un foco de luz que se mueve sobre el contenido en mi aplicación, así:Creación de una cubierta traslúcida animatable con capas Core Animation

Spotlight ASpotlight B

En la aplicación de ejemplo (mostrado arriba), el fondo la capa es azul, y tengo una capa sobre ella que oscurece todo, excepto un círculo que lo muestra normalmente. Tengo esto funcionando (se puede ver cómo en el código a continuación). En mi aplicación real, hay contenido real en otros CALayers, en lugar de solo azul.

Aquí está mi problema: no anima. Estoy usando el dibujo CGContext para crear el círculo (que es una mancha vacía en una capa que de otro modo sería negra). Cuando haces clic en el botón de mi aplicación de muestra, dibujo el círculo con un tamaño diferente en una ubicación diferente.

Me gustaría que se traduzca y escale suavemente, en lugar de saltar, como lo hace actualmente. Puede requerir un método diferente para crear el efecto de foco, o podría haber una forma que no sé para animar implícitamente la llamada -drawLayer:inContext:.

Es fácil para crear la aplicación de ejemplo:

  1. hacer una nueva aplicación del Cacao (usando ARC)
  2. Añadir el marco de cuarzo
  3. gota una vista personalizada y un botón en el XI ter
  4. Vincular la vista personalizada a una nueva clase (SpotlightView), con el código proporcionado a continuación
  5. Eliminar SpotlightView.h, ya que incluí su contenido en SpotlightView.m
  6. Establecer la salida del botón para la acción -moveSpotlight:

actualización (la propiedad mask)

me gusta la sugerencia de David Rönnqvist en los comentarios utilizar la propiedad mask de la capa oscura que cortar un agujero, que Podría moverme de forma independiente. El problema es que, por alguna razón, la propiedad mask funciona de forma opuesta a como espero que funcione una máscara. Cuando especifico una máscara circular, todo lo que aparece es el círculo. Esperaba que la máscara funcionara de la manera opuesta, enmascarando el área con 0 alpha.

Enmascarar se siente como la manera correcta de hacerlo, pero si tengo que rellenar toda la capa y hacer un agujero, puedo hacerlo de la forma en que originalmente lo hice. ¿Alguien sabe cómo invertir la propiedad -[CALayer mask], para que el área dibujada se recorte de la imagen de la capa?

/actualización

Aquí está el código para 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

¿Ha intentado fijar una escala y traducir a transformar en la capa oscura centro de atención? –

+0

@ DavidRönnqvist Eso no funcionará, porque la capa con el foco es del tamaño de todo el 'superlayer' – Dov

+0

¿Podría el foco ser la máscara de la capa oscura? Puede dibujar el reflector en su propia capa y establecerlo como la propiedad -mask: en la capa oscura. De esa manera, creo que puedes animar el tamaño y la posición del reflector independientemente del tamaño y la posición de la capa oscura ... aunque aún no lo hemos probado. –

Respuesta

6

finalmente lo tengo. Lo que me impulsó a la respuesta fue escuchar la clase CAShapeLayer. Al principio, pensé que sería una forma más sencilla de dibujar los contenidos de la capa, en lugar de dibujar y borrar los contenidos de un estándar CALayer.Pero leí la documentación de la propiedad path de CAShapeLayer, que decía que podía ser animada, pero no implícitamente.

Mientras que una máscara de capa podría haber sido más intuitiva y elegante, que no parece que sea posible usar la máscara para ocultar una porción de la capa del propietario, en lugar de mostrando una parte, y por eso no podría usarlo Estoy contento con esta solución, ya que está bastante claro lo que está pasando. Desearía que se usara animación implícita, pero el código de animación es solo de algunas líneas.

A continuación, modifiqué el código de muestra de la pregunta para agregar una animación suave. (I eliminado el código CIFilter, porque era extraña. La solución aún funciona con filtros.)

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

Muchas gracias, ¡esto realmente ayudó! Necesitaba algo para hacer que mi círculo segmentado pareciera contraerse, sin encogerse en realidad. Ahora puedo hacer eso. – Jelle

Cuestiones relacionadas