使用Core Animation图层创建可动画的半透明叠加层

Dov*_*Dov 6 macos cocoa drawing core-animation calayer

我正在创建一个聚焦在我的应用程序中移动内容的聚光灯,如下所示:

聚光灯A. 聚光灯B.

在示例应用程序(如上所示)中,背景图层是蓝色的,我在其上面有一个图层使所有图层变暗,除了正常显示它的圆圈.我有这个工作(你可以在下面的代码中看到).在我的真实应用中,其他CALayers中有实际内容,而不仅仅是蓝色.

这是我的问题:它没有动画.我正在使用CGContext绘图来创建圆圈(这是一个黑色图层中的空白点).当您单击我的示例应用程序中的按钮时,我会在不同位置绘制不同大小的圆圈.

我希望能够顺利地翻译和扩展,而不是像目前那样跳跃.它可能需要一种不同的方法来创建聚光灯效果,或者可能有一种我不知道的方式隐式地为-drawLayer:inContext:调用设置动画.

创建示例应用程序很容易:

  1. 制作一个新的Cocoa应用程序(使用ARC)
  2. 添加Quartz框架
  3. 将自定义视图和按钮拖放到XIB上
  4. 将自定义视图链接到新类(SpotlightView),下面提供了代码
  5. 删除SpotlightView.h,因为我将其内容包含在SpotlightView.m中
  6. 将按钮的出口设置为-moveSpotlight:操作

更新(mask属性)

我喜欢DavidRönnqvist在评论中建议使用mask黑暗层的属性来切出一个洞,然后我可以独立移动.问题在于,由于某种原因,该mask属性与我期望掩码工作的方式相反.当我指定一个圆形蒙版时,所有显示的都是圆形.我期望掩码以相反的方式工作,用0alpha 屏蔽区域.

掩盖感觉是正确的方法,但如果我必须填写整个图层并切出一个洞,那么我也可以按照我最初发布的方式进行操作.有谁知道如何反转-[CALayer mask]属性,以便绘制的区域从图层的图像中被切除?

/更新

这是以下代码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
Run Code Online (Sandbox Code Playgroud)

Dov*_*Dov 6

我终于明白了.是什么促使我回答的是听到CAShapeLayer全班同学的意见.起初,我认为绘制图层内容是一种更简单的方法,而不是绘制和清除标准的内容CALayer.但我阅读了该path属性的文档CAShapeLayer,其中说明它可能是动画的,但不是含蓄的.

虽然图层蒙版可能更直观和优雅,但似乎不可能使用蒙版来隐藏所有者图层的一部分,而不是显示部分,因此我无法使用它.我很满意这个解决方案,因为它很清楚发生了什么.我希望它使用隐式动画,但动画代码只有几行.

下面,我修改了问题中的示例代码以添加平滑动画.(我删除了CIFilter代码,因为它是无关紧要的.解决方案仍然适用于过滤器.)

//
//  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
Run Code Online (Sandbox Code Playgroud)