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

时间:2012-02-25 16:22:19

标签: macos cocoa drawing core-animation calayer

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

Spotlight A Spotlight B

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

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

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

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

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

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

    掩盖感觉是正确的方法,但如果我必须填写整个图层并切出一个洞,那么我也可以按照我最初发布的方式进行操作。有谁知道如何反转-[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
    

1 个答案:

答案 0 :(得分:6)

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

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

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