【问题标题】:Creating an animatable translucent overlay with Core Animation layers使用 Core Animation 图层创建可动画的半透明叠加层
【发布时间】:2012-03-15 18:53:30
【问题描述】:

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

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

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

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

创建示例应用很容易:

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

更新(mask 属性)

我喜欢 David Rönnqvist 在 cmets 中的建议,即使用深色图层的 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

【问题讨论】:

  • 您是否尝试过在暗聚光灯图层上设置比例和平移变换?
  • @DavidRönnqvist 这不行,因为聚光灯的层是整个superlayer的大小
  • 聚光灯可以是暗层的遮罩吗?您可以将聚光灯绘制到它自己的图层中并将其设置为 -mask: 暗图层上的属性。这样一来,我认为您可以为聚光灯的大小和位置设置动画,而不管暗层的大小和位置如何。虽然还没有尝试过。
  • @DavidRönnqvist 这太令人沮丧了,除了我需要反转掩码,而且我没有看到一个简单的方法来做到这一点。如果我必须将整个遮罩层涂黑并在其中切一个洞,那么我又回到了开始的地方。
  • 如果它是一个选项,您可以获得背景的图像表示(上面的蓝色)并在黑暗层上再次绘制它并使用蒙版将其限制为聚光灯。这样您就可以为蒙版制作动画,但只有在下面的内容相当静态时才会起作用

标签: macos cocoa drawing core-animation calayer


【解决方案1】:

我终于明白了。促使我回答的原因是听说过 CAShapeLayer 课程。起初,我认为绘制图层内容会更简单,而不是绘制和清除标准CALayer 的内容。但我阅读了CAShapeLayerpath 属性的文档,其中指出它可以动画,但不是隐式的。

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

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

【讨论】:

  • 非常感谢,这真的很有帮助!我需要一些东西来让我的分段圆看起来缩小,而不是实际缩小。现在我可以做到了。
猜你喜欢
  • 1970-01-01
  • 2023-04-03
  • 2015-04-21
  • 1970-01-01
  • 1970-01-01
  • 2012-06-12
  • 2015-03-18
  • 1970-01-01
相关资源
最近更新 更多