Macos 使用核心动画层创建可设置动画的半透明覆盖

Macos 使用核心动画层创建可设置动画的半透明覆盖,macos,cocoa,drawing,core-animation,calayer,Macos,Cocoa,Drawing,Core Animation,Calayer,我正在创建一个聚光灯,它可以覆盖我应用程序中的内容,如下所示: 在示例应用程序(如上所示)中,背景层是蓝色的,我在它上面有一个层,使所有的背景层都变暗,除了一个正常显示的圆圈。我已经做到了(你可以在下面的代码中看到)。在我真正的应用程序中,其他Calayer中有实际内容,而不仅仅是蓝色 我的问题是:它没有动画。我正在使用CGContextdrawing来创建圆(这是黑色图层中的一个空白点)。当你在我的示例应用程序中单击按钮时,我会在不同的位置以不同的大小绘制圆圈 我希望它能够平滑地转换和缩放,

我正在创建一个聚光灯,它可以覆盖我应用程序中的内容,如下所示:

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

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

我希望它能够平滑地转换和缩放,而不是像现在那样跳跃。它可能需要一种不同的方法来创建聚光灯效果,或者可能有一种我不知道的方法来隐式设置
-drawLayer:inContext:
调用的动画

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

  • 制作新的Cocoa应用程序(使用ARC)
  • 添加石英框架
  • 将自定义视图和按钮放到XIB上
  • 使用下面提供的代码将自定义视图链接到新类(SpotlightView)
  • 删除
    SpotlightView.h
    ,因为我在SpotlightView.m中包含了它的内容
  • 将按钮的出口设置为
    -moveSpotlight:
    操作
  • 更新(
    掩码
    属性)

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

    掩蔽看起来是正确的方法,但是如果我必须填充整个层并切出一个洞,那么我也可以按照我最初发布的方式来做。有人知道如何反转
    -[CALayer mask]
    属性,以便从图层图像中剪切绘制的区域吗

    /Update

    以下是
    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
    
    //
    //SpotlightView.m
    //
    #进口
    @接口SpotlightView:NSView
    -(iAction)移动聚光灯:(id)发送方;
    @结束
    @接口SpotlightView()
    @属性(强)CALayer*spotlightLayer;
    @属性(分配)CGRect highlightRect;
    @结束
    @实现SpotlightView
    @合成聚光灯;
    @合成highlightRect;
    -(id)initWithFrame:(NSRect)帧{
    if((self=[super initWithFrame:frame])){
    self.wantsLayer=是;
    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*模糊过滤器=[CIFilter过滤器名称:@“CIGaussianBlur”];
    [blurFilter设置值:[NSNumber numberWithFloat:5.0]
    福基:@“inputRadius”];
    self.spotlightLayer.filters=[NSArray arrayWithObject:blurFilter];
    [self.layer addSublayer:self.spotlightLayer];
    }
    回归自我;
    }
    -(void)drawRect:(NSRect)dirtyRect{}
    -(无效)移动聚光灯:(id)发送者{
    [self.spotlightLayer设置需要显示];
    }
    -(void)drawLayer:(CALayer*)层inContext:(CGContextRef)ctx{
    如果(层==自发光层){
    CGContextSaveGState(ctx);
    CGColorRef blackColor=CGColorCreateGenericGray(0.0,1.0);
    CGContextSetFillColorWithColor(ctx,黑色);
    CGColorRelease(黑色);
    CGContextClearRect(ctx,layer.bounds);
    CGContextFillRect(ctx,layer.bounds);
    //导致切换
    if(CGRectIsNull(self.highlightRect)| self.highlightRect.origin.x!=25){
    self.highlightRect=CGRectMake(25,25100100);
    }否则{
    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);
    }
    否则{
    CGColorRef blueColor=CGColorCreateGenericRGB(0,0,1.0,1.0);
    CGContextSetFillColorWithColor(ctx,蓝色);
    CGContextFillRect(ctx,layer.bounds);
    CGColorRelease(蓝色);
    }
    }
    @结束
    
    我终于拿到了。促使我回答这个问题的是听到了全班的情况。起初,我认为绘制图层内容比绘制和清除标准
    CALayer
    的内容更简单。但是我阅读了
    CAShapeLayer
    path
    属性的文档,其中说明它可以设置动画,但不能隐式设置

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

    下面,我修改了问题中的示例代码以添加平滑动画。(我删除了
    CIFilter
    代码,因为它是extrane
    //
    //  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